見出し画像

情報で捉える生物学入門#10 【進化学】

生物を作り上げる「情報」遺伝子とその総体のゲノムとして次世代に受け継がれ、その「情報」が突然変異により変化することで進化が起こる。生物集団内で受け渡される情報が変化すれば、それは例え生物集団の表現型になんの変化が生じていなくても進化が起こっており、小進化といわれる。一方で、新しい種やそれ以上の分類群の出現といった、私たちが普段イメージするような表現型の大きな変化を伴う、長いタイムスケールで起きる進化が大進化である。進化は様々なありえた生物形態の中で、なぜ現実の地球上生物が生じたのかを説明することが出来る唯一の理論であるという点で重要である。


自然選択

古代より、多様な生物は神の創造により作られた不変なものであるとされてきた。1809年、ラマルクは「動物哲学」で、生物は不変なものではなく進化すると述べたが、その理論は現在受け入れられていない。1859年、ダーウィンは彼の著書「種の起源」で、生物は自然選択によって進化することを提唱した。それ以後現在まで自然選択は生物進化の中心的な機構であると受け入れられている。自然選択は、以下の3つの条件で生じる。

  1. 生物個体間の表現型に違いがある。(変異)

  2. 表現型の一部が次世代に伝わる。(遺伝)

  3. 表現型の違いにより次世代に残る子の数に違いが生じる。(選択)

イギリスに生息するオオシモフリエダシャクというガは、表現型として明るい体色の明色型と暗い体色の暗色型が知られていたが、その多くは明色型だった。しかし、生息地の工業化によって樹皮を覆っていた地衣類が枯れて樹皮が黒っぽくなり、明色型は捕食者に見つかる確率が高くなってしまった。これにより明色型の適応度が下がって暗色型の頻度が世代を通じて増えていく適応進化が起こった。これは、工業暗化と呼ばれ、自然選択の例として知られている。また、ここからも明らかなように、進化は生息環境への適応であって一般に優れた表現型を獲得するという意味ではない。

自然選択の一種に、各表現型の個体数に依存して適応度が変化する自然選択である頻度依存選択がある。集団内での割合が多い多数派が有利になる場合を正の頻度依存選択、少数派が有利になる場合を負の頻度依存選択という。他の魚の鱗をはぎ取って食べる魚は、遺伝的に口が右曲がりの個体と左曲がりの個体が存在する。被食される側の魚は右側から近づいて食べる個体が増えるとそちらが警戒されてそれらの個体の適応度が下がるため、これは負の頻度依存選択の例であると知られている。正の頻度依存選択では多数派が増加していき、負の頻度依存選択では多数派と少数派が世代を経るごとに逆転して複数のタイプの個体が維持される。勘のいい読者は気づいたかもしれないが、これはまさに正のフィードバックと負のフィードバックの例であり、ここでも生物の様々な階層において同様の情報処理が繰り返し使いまわされていることが分かると思う。

遺伝的浮動と中立進化

遺伝的浮動は、集団内の遺伝子頻度の世代を通じた確率的な変化である。遺伝的浮動は以下に挙げる3つの条件で生じるが、自然選択と異なるのは3つ目の条件のみである。

  1. 生物個体間の表現型に違いがある。(変異)

  2. 表現型の一部が次世代に伝わる。(遺伝)

  3. 次世代に残る子の数に確率的な違いが生じる。(浮動)

生物は精度の高いDNA修復機構を持つが、それでも膨大なゲノム情報を保持する(ヒトでは約$${3\times10^9}$$塩基・$${6\times10^9}$$ビット)ため、突然変異と呼ばれるエラーが引き起こされる。ゲノム中に起きる突然変異は、そのほとんどが個体にとって有利でも不利でもなく中立なため、自然選択が働かない。これを「ゲノム進化の中立説」といい、自然選択とは独立な進化機構として認知されている。ゲノムに生じたほとんどの中立突然変異は遺伝的浮動の過程で取り除かれるが、一部の突然変異は集団全体に広がって固定され、その過程にある突然変異は遺伝的多型として検出される。

シミュレーション1:コイントス

確率的な影響は集団サイズが小さい時に強く働くため、遺伝的浮動は集団サイズが小さい時に大きな効果を持つ。これは、コイン投げの例を考えると分かりやすい。以下のGoogle Colabで動くPythonシミュレーションのように、コイン投げの回数が10回以下だったらどちらかの面が8割出ても驚かないが、100回を超えてそのようなことが起こればコインに何らかの細工を疑うだろう。

!pip install japanize-matplotlib

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

# コイントスの回数を2の累乗(2^n)に設定する
toss_counts = np.array([2**n for n in range(1, 14)])  # 2, 4, 8, ..., 8192
trials_per_count = 8

# 表の割合を保存するデータを準備する
ratios = []

# 試行を実行する
for toss_count in toss_counts:
    trial_ratios = []
    for _ in range(trials_per_count):
        heads_count = np.random.binomial(n=toss_count, p=0.5)
        trial_ratios.append(heads_count / toss_count)
    ratios.append(trial_ratios)

# 結果を散布図としてプロット
plt.figure(figsize=(10, 6))

for i in range(trials_per_count):
    plt.scatter(toss_counts, [ratios[j][i] for j in range(len(toss_counts))], alpha=0.7, s=50, color="black")

# y=0.5 に水平の破線をプロットする
plt.axhline(y=0.5, color="gray", linestyle="--")

# プロットをカスタマイズする
plt.xscale('log')
plt.xlabel("コイントスの回数 (対数スケール)")
plt.ylabel("表の割合")
plt.title("コイントス試行における表の割合")

# プロットを表示する
plt.show()

シミュレーション2:遺伝的浮動

同様に、遺伝的浮動が強く働くと確率的に対立遺伝子の頻度が大きく変動し、最終的にはいずれかの対立遺伝子への固定が起こる。集団がランダム交配する際の世代を経た対立遺伝子Aの頻度変化をシミュレーションした以下の結果は、対立遺伝子の頻度の初期値0.5からのずれが個体数が多いほど小さく、個体数が少ない場合は10世代という非常に少ない世代で固定が起こる場合があることが分かる。

近年はヒトにより動物史上で第6の大量絶滅が起きているといわれており、生物保全が盛んにおこなわれている。個体数が数百・千匹いても絶滅危惧種等に分類され、早めに保全がなされることに疑問を持つ読者もいるかもしれないが、集団サイズが小さくなると遺伝的浮動により遺伝的多様性(情報)が失われ、少しの環境の変化に耐えられず絶滅の危険が高まるということが、このシミュレーションからわかるのではないかと思う。その意味で、「生物多様性がなぜ大切か」は、40億年の進化を通して最適化された多様な局所最適近傍の情報が含まれているからとも捉えられるだろう。

# パラメータの設定
np.random.seed(42)  # 再現性を確保するために乱数シードをリセット
generations = 10  # 世代数
initial_freq_A = 0.5  # 対立遺伝子Aの初期頻度(1:1の比率)
population_sizes = [2**2, 2**4, 2**8]  # 個体数: 4, 16, 256
trials = 8  # 各個体数での試行回数

# 異なる個体数に対する色の設定
colors = ["blue", "green", "red"]
labels = [f"個体数 = {N}" for N in population_sizes]

# 個体数ごとに色を合わせた凡例付きでプロット
plt.figure(figsize=(10, 6))

# 各個体数に対するシミュレーションを実行
for idx, N in enumerate(population_sizes):
    for trial in range(trials):
        # 初期集団の遺伝子型を初期化(ハーディ・ワインベルグ平衡に基づく)
        freq_AA = initial_freq_A ** 2
        freq_Aa = 2 * initial_freq_A * (1 - initial_freq_A)
        freq_aa = (1 - initial_freq_A) ** 2
        population = np.random.choice(['AA', 'Aa', 'aa'], size=N, p=[freq_AA, freq_Aa, freq_aa])

        # 現在の試行で各世代の対立遺伝子Aの頻度を保存
        num_A = 2 * np.sum(population == 'AA') + np.sum(population == 'Aa')
        freq_A_sim = [num_A / (2 * N)]

        # 各世代をシミュレーション
        for generation in range(1, generations):

            new_population = []

            # 子孫生成をシミュレーション
            for _ in range(N):
                # ランダムに親を選択
                parent1 = np.random.choice(population, p=np.ones(N)/N) #size=1, replace=False)
                parent2 = np.random.choice(population, p=np.ones(N)/N) #size=1, replace=False)

                # 親から対立遺伝子を一つずつ受け継ぐ
                allele1 = np.random.choice(list(parent1))
                allele2 = np.random.choice(list(parent2))

                # 子の遺伝子型を作成(アルファベット順にソート)
                offspring_genotype = ''.join(sorted([allele1, allele2]))
                new_population.append(offspring_genotype)

            # 集団を次世代に更新
            population = np.array(new_population)

            # 対立遺伝子Aの頻度を計算
            num_A = 2 * np.sum(population == 'AA') + np.sum(population == 'Aa')
            freq_A = num_A / (2 * N)
            freq_A_sim.append(freq_A)

        # 現在の試行結果をプロット
        plt.plot(range(1, generations + 1), freq_A_sim, marker='o', linestyle='-', color=colors[idx], alpha=0.6)

# プロットのカスタマイズ
plt.xlabel("世代")
plt.ylabel("対立遺伝子Aの頻度")
plt.title("異なる個体数における世代ごとの対立遺伝子Aの頻度変化(遺伝的浮動、二倍体)")
plt.axhline(y=0.5, color="gray", linestyle="--")  # y=0.5の参照線
plt.ylim(0, 1)  # y軸の範囲を0から1に設定
plt.xticks(range(1, generations + 1))

# 色を合わせた凡例を追加
for idx, label in enumerate(labels):
    plt.plot([], [], color=colors[idx], label=label)

plt.legend(loc="upper left", fontsize=8)
plt.grid(False)

plt.show()

シミュレーション3:自然選択

最後に、この集団に対して自然選択が働く場合を考える。対立遺伝子Aが顕性(優性)だとして、この対立遺伝子を持っている個体が持っていない個体に比べてb倍子孫を残しやすいとしたシミュレーションを以下に示す。10世代で対立遺伝子Aの頻度が増える正の自然選択が働いていることが分かる。また、個体数が少ない青線の集団では、遺伝的浮動が強く働くため逆に対立遺伝子Aが集団から消失する場合もあることも分かる。コードはGoogle Colabで動くので、ぜひ対立遺伝子Aの適応度bなどのパラメーターを変えて、遺伝的浮動と自然選択の影響力の大きさがどのように変化するか自分で確かめてみてほしい。

# パラメータの設定
np.random.seed(42)  # 再現性を確保するために乱数シードをリセット
generations = 10  # 世代数
initial_freq_A = 0.5  # 対立遺伝子Aの初期頻度(1:1の比率)
population_sizes = [2**2, 2**4, 2**8]   # 個体数: 4, 16, 256
trials = 8  # 各個体数での試行回数
b = 4.0  # 遺伝子型Aの増殖しやすさ

# 異なる個体数に対する色の設定
colors = ["blue", "green", "red"]
labels = [f"個体数 = {N}" for N in population_sizes]

# 個体数ごとに色を合わせた凡例付きでプロット
plt.figure(figsize=(10, 6))

# 各個体数に対するシミュレーションを実行
for idx, N in enumerate(population_sizes):
    for trial in range(trials):
        # 初期集団の遺伝子型を初期化(ハーディ・ワインベルグ平衡に基づく)
        freq_AA = initial_freq_A ** 2
        freq_Aa = 2 * initial_freq_A * (1 - initial_freq_A)
        freq_aa = (1 - initial_freq_A) ** 2
        population = np.random.choice(['AA', 'Aa', 'aa'], size=N, p=[freq_AA, freq_Aa, freq_aa])

        # 現在の試行で各世代の対立遺伝子Aの頻度を保存
        num_A = 2 * np.sum(population == 'AA') + np.sum(population == 'Aa')
        freq_A_sim = [num_A / (2 * N)]

        # 各世代をシミュレーション
        for generation in range(1, generations):
            # 個体ごとの適応度を計算
            fitnesses = np.array([b if genotype in ['AA', 'Aa'] else 1 for genotype in population])
            probabilities = fitnesses / fitnesses.sum()

            new_population = []

            # 子孫生成をシミュレーション
            for _ in range(N):
                # 適応度に基づいて親を選択
                parent1 = np.random.choice(population, p=probabilities)
                parent2 = np.random.choice(population, p=probabilities)

                # 親から対立遺伝子を一つずつ受け継ぐ
                allele1 = np.random.choice(list(parent1))
                allele2 = np.random.choice(list(parent2))

                # 子の遺伝子型を作成(アルファベット順にソート)
                offspring_genotype = ''.join(sorted([allele1, allele2]))
                new_population.append(offspring_genotype)

            # 集団を次世代に更新
            population = np.array(new_population)

            # 対立遺伝子Aの頻度を計算
            num_A = 2 * np.sum(population == 'AA') + np.sum(population == 'Aa')
            freq_A = num_A / (2 * N)
            freq_A_sim.append(freq_A)

        # 現在の試行結果をプロット
        plt.plot(range(1, generations + 1), freq_A_sim, marker='o', linestyle='-', color=colors[idx], alpha=0.6)

# プロットのカスタマイズ
plt.xlabel("世代")
plt.ylabel("対立遺伝子Aの頻度")
plt.title("異なる個体数における世代ごとの対立遺伝子Aの頻度変化(自然選択、二倍体)")
plt.axhline(y=0.5, color="gray", linestyle="--")  # y=0.5の参照線
plt.ylim(0, 1)  # y軸の範囲を0から1に設定
plt.xticks(range(1, generations + 1))

# 色を合わせた凡例を追加
for idx, label in enumerate(labels):
    plt.plot([], [], color=colors[idx], label=label)

plt.legend(loc="upper left", fontsize=8)
plt.grid(False)

plt.show()

参考文献

生物の進化は、ゲノム情報が突然変異や遺伝的浮動によって変化し、それが自然選択などの選択圧を受けて集団内の対立遺伝子頻度が変動することで進行する。自然選択は表現型の差異や適応度に基づいて特定の形質が優勢になる過程を説明し、遺伝的浮動は特に小規模集団において確率的に遺伝子頻度を変化させる要因として重要である。これらの進化的力学のシミュレーションは、集団サイズや適応度の差が進化速度や遺伝的多様性に与える影響を示し、生物多様性の重要性や進化過程の複雑さを浮き彫りにしている。

ChatGPTを用いて要約
サムネイル画像はDALL-Eにより生成