天気予報や地図アプリの経路探索など、私たちは日常的にコンピューター・シミュレーションの恩恵を受けています。シミュレーションの面白いところは、現実世界では何度も試すことが難しい現象を、コンピューター上で繰り返し検証できることです。
で、これを民主主義や選挙制度の分析に使うとなにか見えるのか、というのが今回の興味です。
この記事では、その中でも特に興味深かった次のテーマを、Pythonで簡単にシミュレーションしてみます。
- 二大政党制に第三党を追加すると何が起こるか
- 相対多数制と順位選択投票で結果はどう変わるか
- 同じ得票率でも選挙区割りによって議席数が変わるのか
- 多数の区割り案から「極端な案」を統計的に見つけられるか
あくまで教育目的の単純化したモデルですが、選挙制度が単なる「票を数える仕組み」ではないことがよく分かります。
民主主義を「複雑系」として考える
脳は、膨大な数の神経細胞が互いに影響しながら動く複雑なシステムです。
一つひとつの神経細胞だけを見ても予測できない現象が、集団になることで発生します。たとえば、多数の神経細胞が同期して活動することが、発作につながる場合があります。
社会でも、一人ひとりは独立して判断しているように見えて、実際には、
- 周囲の人々
- 政党
- 候補者
- メディア
- SNS
- 選挙制度
- 社会的な空気
などから影響を受けています。そのため、多くの人が同じ方向へ一斉に動き始めると、個人の行動だけからは予想しにくい大きな現象が起こる可能性があります。民主主義も、単純に「独立した有権者が一票ずつ投じるシステム」ではなく、複数の要素が相互作用する複雑系として考えられるわけです。
そして複雑系では、
直感的に良さそうな改革が、必ずしも良い結果を生むとは限らない
という問題が出てきます。
そこで、制度を変更する前にシミュレーションしてみよう!というのが今回のテーマです。
民主主義を評価する3つの視点
民主主義に必要な機能として挙げられる三つの要素は:
1. 代表性(Representation)
社会を構成する多様な人々の意見が、政治に反映されているか。
2. 応答性(Responsiveness)
有権者が考えを変えたり、政治家に不満を持ったりしたとき、選挙によって政治を変えられるか。
3. 熟議性(Deliberation)
異なる立場の政治家が議論し、妥協しながら問題を解決できるか。
選挙制度を評価するとき、単純に「誰が勝ったか」だけを見るのでは不十分です。制度そのものが、有権者や候補者の行動をどう変えるのかを見る必要があります。
有権者は本当に「左か右か」なのか
政治はしばしば、次のような一次元の軸で説明されます。
リベラル ←────────────→ 保守
政治的に意識高い人は、すぐに「右か左か?」と人をカテゴライズしているように見えます。
しかし実際の政治的立場は、そんなに単純ではありません。
ある人は、
- 経済政策では保守的
- 環境政策ではリベラル
- 社会保障には賛成
- 文化的価値観では伝統的
という組み合わせを持つかもしれません。
つまり本来、有権者は多次元的な存在です。
ところが米国のような二大政党制では、最終的に二つの大きな選択肢のどちらかを選ぶことになります。そのため、
どちらにも完全には賛成していないが、比較的近い方に投票する
という行動が繰り返されます。
結果だけを見ると、毎回同じ政党に投票する「忠実な支持者」に見えても、実際には単に二択の中で近い方を選び続けているだけかもしれません。
この構造が、政治的分極化と関係しているのではないか、というのが講演の一つの問題提起です。
第三党を作れば解決するのか
二大政党の対立が問題なら、
第三党を追加すればよいのでは?
と思います。しかし、現在の選挙制度をそのままにして第三党だけを追加すると、逆効果になる場合があります。
ここで登場するのがスポイラー効果です。
たとえば、
赤 46%
青 44%
第三党 10%
という選挙を考えます。青と第三党が比較的近い政治的立場だったとしても、相対多数制では最も多くの票を得た赤が勝ちます。
青と第三党を合わせれば54%なので、有権者の過半数が赤とは異なる方向を支持しているにもかかわらず、票が分裂したことで赤が当選します。このように、第三候補が自分に近い候補の票を奪い、結果的に遠い候補を有利にしてしまう現象をスポイラー効果と呼びます。
では、本当にこのようなことが起こるのでしょうか。
ということで、Pythonで試してみます。
Pythonでスポイラー効果を再現する
モデルの前提
今回は、有権者と候補者を一次元の政治空間上に配置します。候補者の位置は次のようにします。
青 第三党 赤
-0.8 -0.25 0.8
数値が小さいほど左寄り、大きいほど右寄りです。第三党は中央寄りですが、赤より青に少し近い位置するとします。有権者については、青寄りと赤寄りの2つの集団から生成します。
また、有権者は単純に、
自分の位置から最も近い候補者へ投票する
と仮定します。当然ながら現実の投票行動はもっと複雑です。今回はあくまで、スポイラー効果の構造を見るための簡易モデルです。
有権者を生成する
import numpy as np
import pandas as pd
SEED = 42
def generate_electorate(
rng,
n_voters=1200,
swing_sd=0.15,
voter_sd=0.9
):
# 選挙ごとの全体的な左右の揺れ
swing = rng.normal(0, swing_sd)
# 半分を左側、半分を右側の集団として生成
left_cluster = rng.random(n_voters) < 0.5
return np.where(
left_cluster,
rng.normal(-0.9 + swing, voter_sd, n_voters),
rng.normal(0.9 + swing, voter_sd, n_voters),
)
ここでは有権者を2つの正規分布から生成しています。毎回完全に同じ有権者では面白くないので、swing を使って選挙ごとに全体を少し左右へ動かしています。
各有権者が最も近い候補者を選ぶ
def first_choice_counts(voter_positions, candidate_positions):
distances = np.abs(
voter_positions[:, None]
- np.asarray(candidate_positions)[None, :]
)
rankings = np.argsort(distances, axis=1)
counts = np.bincount(
rankings[:, 0],
minlength=len(candidate_positions)
)
return counts, rankings
各有権者と候補者の距離を計算し、最も近い候補者を第1希望とします。
rankings には第2希望、第3希望も残しておきます。これは後で順位選択投票に使います。
順位選択投票も実装する
順位選択投票では、最下位候補が脱落したとき、その候補への票を次順位へ移します。簡易的なInstant Runoff Votingを実装します。
def instant_runoff_winner(rankings, n_candidates):
active = set(range(n_candidates))
while True:
counts = np.zeros(n_candidates, dtype=int)
for ballot in rankings:
for candidate in ballot:
if candidate in active:
counts[candidate] += 1
break
total = counts[list(active)].sum()
leader = max(active, key=lambda c: counts[c])
if (
counts[leader] > total / 2
or len(active) == 2
):
return leader
lowest = min(
active,
key=lambda c: counts[c]
)
active.remove(lowest)
今回は3候補なので、最下位が脱落すると、残った2候補のうち各有権者がより高く順位づけしていた方へ票が移ります。
1万回選挙を実行する
rng = np.random.default_rng(SEED)
repetitions = 10_000
names2 = ["青", "赤"]
positions2 = [-0.8, 0.8]
names3 = ["青", "赤", "第三党"]
positions3 = [-0.8, 0.8, -0.25]
wins2 = dict.fromkeys(names2, 0)
plurality = dict.fromkeys(names3, 0)
ranked = dict.fromkeys(names3, 0)
for _ in range(repetitions):
electorate = generate_electorate(rng)
# 二大政党
counts2, _ = first_choice_counts(
electorate,
positions2
)
wins2[
names2[int(np.argmax(counts2))]
] += 1
# 第三党あり
counts3, rankings = first_choice_counts(
electorate,
positions3
)
plurality[
names3[int(np.argmax(counts3))]
] += 1
# 順位選択投票
ranked[
names3[
instant_runoff_winner(
rankings,
3
)
]
] += 1
最後に勝率へ変換します。
results = pd.DataFrame({
"二大政党・相対多数制":
pd.Series(wins2),
"第三党あり・相対多数制":
pd.Series(plurality),
"第三党あり・順位選択投票":
pd.Series(ranked),
}).fillna(0) / repetitions * 100
results
結果
実行結果は次のようになりました。
| 二大政党・相対多数制 | 第三党あり・相対多数制 | 第三党あり・順位選択投票 | |
|---|---|---|---|
| 青 | 50.06% | 21.21% | 50.06% |
| 赤 | 49.94% | 78.79% | 49.94% |
| 第三党 | 0% | 0% | 0% |
二大政党だけなら、青と赤の勝率はほぼ50対50です。ところが第三党を追加すると、
青 約21%
赤 約79%
第三党 0%
まで変化しました。
重要なのは、赤の支持者を増やしていないことです。第三党の登場によって主として青側の票が分割され、赤が相対的に最多得票になりやすくなっただけです。
これがスポイラー効果です。
なぜ第三党は0勝なのか
この結果を見たとき、
さすがに第三党が1万回中0勝なのはおかしくないか?
と思いました。しかし、今回のモデルを確認すると理由は明確です。
有権者は、
- 青寄りの山
- 赤寄りの山
の2つからしか生成していません。つまり第三党には、独自の支持者クラスタがありません。イメージとしては次のような状態です。
青寄りの山 赤寄りの山
/\ /\
/ \ / \
───/────\────第三党───────────────────/────\───
第三党は2つの山の間に存在します。そのため一定の票を奪うことはできますが、自ら最大勢力になるほどの支持基盤がありません。つまり、第三党の0勝は計算ミスではありません。
ただし重要なのは、
「第三党は一般的に勝てない」
という結果ではないことです。
今回のモデルは、
独自の支持者集団を持たず、既存政党に近い票を分割する第三党
を意図的に作ったモデルです。
スポイラー効果を観察するための模型、と考えるべきでしょう。第三党そのものの可能性を評価したいのであれば、有権者側にも第三のクラスタを追加する必要があります。
順位選択投票にするとどうなるか
興味深いのはここからです。有権者も候補者もまったく変えず、票の数え方だけを順位選択投票に変更します。すると、
青 50.06%
赤 49.94%
第三党 0%
となり、ほぼ元の二大政党時の勝率に戻りました。
なぜでしょうか。
今回のモデルでは、第三党が脱落すると、その支持者は青か赤のうち自分に近い方へ票を移します。
つまり、
相対多数制
青票 + 第三党票
↓
分裂したまま集計
順位選択投票
第三党が脱落
↓
第2希望へ移動
↓
青と赤の二者択一に近づく
という違いがあります。ここで重要なのは、
有権者の思想は何も変わっていない
ということです。
変えたのは集計方式だけです。それでも選挙結果は大きく変わります。選挙制度は単に民意を受動的に数える仕組みではなく、民意を結果へ変換するアルゴリズムでもある、ということがよく分かります。
ゲリマンダリングを数学で見抜けるか
次に、選挙区割りについて考えます。
アメリカでは、一つの選挙区から一人の議員を選ぶ小選挙区制が広く使われています。問題は、選挙区の境界線をどう引くかによって、議席数が大きく変わることです。特定の政党や集団を不利にするよう、意図的に選挙区を設計することをゲリマンダリングと呼びます。
代表的な手法の一つがpackingです。相手政党の支持者を少数の選挙区へ集中させます。その選挙区では相手が圧勝します。
しかし、一つの選挙区から得られる議席は一つだけです。
たとえば、
青 51 : 赤 49
でも、
青 90 : 赤 10
でも、青が獲得するのは同じ1議席です。だったら相手の票を少数の選挙区へ大量に押し込み、残りを僅差で勝てばよい。これがpackingの基本的な考え方です。
Pythonで2万通りの区割りを作る
今回は次の条件を設定します。
- 100の投票区
- 10の選挙区
- 1選挙区につき10投票区
- 全10議席
- 地域全体では青52%、赤48%
まず、各投票区に青の得票率を設定します。
precinct_rng = np.random.default_rng(123)
plan_rng = np.random.default_rng(456)
crafted_rng = np.random.default_rng(789)
shares = precinct_rng.beta(
2.5,
2.3,
100
)
shares = (
shares
- shares.mean()
+ 0.52
)
shares = np.clip(
shares,
0.01,
0.99
)
shares = (
shares
- shares.mean()
+ 0.52
)
全体平均が52%になるよう調整しています。
無作為な区割りを2万回生成する
seat_counts = []
for _ in range(20_000):
districts = shares[
plan_rng.permutation(100)
].reshape(10, 10)
blue_seats = int(
(
districts.mean(axis=1)
> 0.5
).sum()
)
seat_counts.append(blue_seats)
100投票区をランダムに並べ替え、10個ずつまとめます。それぞれの選挙区で青の平均得票率が50%を超えれば、青が1議席獲得したものとします。
結果:52%の得票でも議席数は一定ではない
2万通りの区割りを生成すると、青の議席数は主に5〜7議席に集まりました。平均は約6.27議席です。
青の全体得票率 52%
平均獲得議席 約6.27議席
典型的な結果 5〜7議席
ここで分かるのは、
得票率52%だから必ず5.2議席になる
わけではない、ということです。
小選挙区制では各選挙区の勝敗が0か1になるため、得票率と議席率の間には非線形な関係が生まれます。同じ有権者分布でも、区割りによって5議席になることも、7議席になることもあります。
packingを模した区割りを作る
次に、青に有利な投票区を40個取り出し、4つの選挙区へ集中させます。
order = np.argsort(shares)
packed = shares[
order[-40:]
][
crafted_rng.permutation(40)
].reshape(4, 10)
remaining = shares[
order[:-40]
][
crafted_rng.permutation(60)
].reshape(6, 10)
crafted_districts = np.concatenate([
packed.mean(axis=1),
remaining.mean(axis=1)
])
crafted_seats = int(
(
crafted_districts > 0.5
).sum()
)
すると、青は地域全体で52%の票を持っているにもかかわらず、
青 4議席
赤 6議席
という結果になりました。青の票が消えたわけではありません。青の支持者が少数の選挙区に集中し、そこで「勝ちすぎている」のです。
なぜ「勝ちすぎ」が不利になるのか
一つの選挙区に100票あるとします。
青 51
赤 49
なら青の勝利です。
一方、
青 90
赤 10
でも、青が得るのは同じ1議席です。後者では勝利に必要な51票を大幅に超えています。このような票は、議席獲得という観点では「余剰票」として扱うことができます。
ゲリマンダリングでは、
相手には少数の選挙区で圧勝させ、自分は多数の選挙区で僅差勝ちする
ことで、州全体の得票率とは異なる議席構成を作ることができます。
2万通りの中で4議席はどれくらい珍しいのか
今回の2万通りのランダム区割りでは、青が4議席以下になる案は約2.6%でした。つまり、およそ97%の区割りでは青は5議席以上を獲得しています。その中で4議席という結果は、分布のかなり端に位置しています。統計的には、こうした値を**外れ値(outlier)**として扱うことができます。
典型的な区割り
青 5〜7議席
意図的に作った案
青 4議席
ただし、
4議席になったからゲリマンダリングである
と自動的に断定できるわけではありません。無作為な区割りでも、低い確率では4議席になります。数学ができるのは、
この案は、他の多数の可能な案と比較してどれほど極端なのか
を定量的に示すことです。その結果をどう評価するかは、法律、政治、地理、地域社会などを含めて人間が判断する必要があります。
この区割りシミュレーションの限界
今回のモデルには、かなり大きな単純化があります。
特に重要なのは、地理を考慮していない点です。
100の投票区を完全にランダムに組み合わせているため、現実なら隣接していない地域同士が同じ選挙区になる場合があります。実際の区割りでは、
- 人口の均等性
- 地理的な連続性
- 自治体境界
- 地域共同体
- 少数派の代表性
- 法律上の制約
などを考慮しなければなりません。したがって、今回の2.6%という数字を現実の選挙区に直接当てはめることはできません。ここで見たいのは数字そのものではなく、
同じ総得票率でも区割りによって議席数は変わる
そして、
多数の可能な区割りと比較することで、極端な案を統計的に発見できる
という構造です。
選挙制度は候補者の行動も変える
講演で興味深かったもう一つのポイントが、制度は結果だけでなく候補者の行動まで変えるという点です。たとえば政党別予備選挙では、候補者はまず自党の熱心な支持者に選ばれる必要があります。すると、
幅広い有権者に好かれる
ことよりも、
自党のコア支持者から強く支持される
ことの方が重要になる場合があります。
一方、全政党参加型予備選挙と順位選択投票を組み合わせた制度では、他候補の支持者から第2希望として選ばれることにも意味があります。すると、
相手を徹底的に攻撃する
より、
相手の支持者にも最低限受け入れてもらう
方が合理的な戦略になる可能性があります。重要なのは、政治家が急に穏やかな人格になるわけではないことです。
制度が変わることで、合理的な戦略が変わるわけです。
これはソフトウェアやゲーム設計にも近い話だと思います。ユーザーに「良い行動をしてください」とお願いするより、良い行動を取った方が得になるルールを作る。
民主主義の制度設計にも同じ視点が使えるのかもしれません。
シミュレーションは民主主義の正解を決められるか
もちろん、ここには大きな注意点があります。
数学やシミュレーションが、
最も正しい民主主義とは何か
を自動的に決めてくれるわけではありません。
たとえば、
- 多数派の意思をどこまで重視するか
- 少数派の代表をどう保障するか
- 地域代表と全国得票のどちらを優先するか
- 安定性と多様性をどう両立するか
といった問題は、価値判断を含みます。さらに、シミュレーション結果はモデルの仮定に依存します。
今回のスポイラー効果のモデルでも、
- 有権者を一次元で表現
- 有権者を2つの正規分布から生成
- 最も近い候補へ必ず投票
- 投票率は全員同じ
- 戦略投票なし
- 候補者の人気や人格を無視
という大胆な単純化をしています。第三党が0勝だったのも、この仮定によるものです。
つまり、
計算は客観的でも、何を計算するかは人間が決めている
という点を忘れてはいけません。
それでもシミュレーションには意味がある
では、単純化されたモデルに意味はないのでしょうか。むしろ逆で、単純だからこそ見える構造があります。
今回のシミュレーションでは、
スポイラー効果
第三候補が追加される
↓
近い候補同士で票が分裂する
↓
遠い候補が有利になる
順位選択投票
第三候補が脱落する
↓
第2希望へ票が移る
↓
票の分裂が緩和される
ゲリマンダリング
全体得票率は同じ
↓
支持者の配置だけを変える
↓
獲得議席数が変わる
というメカニズムを見ることができました。現実そのものを完全に再現するのではなく、
ある制度上のメカニズムだけを取り出して理解する
という用途では、単純なシミュレーションはかなり有効だと思います。
数学は民主主義の「GPS」になれるか
GPSは目的地を決めてくれません。どこへ行くかを決めるのは人間です。
しかし、
- この道は渋滞している
- こちらには危険がある
- 別のルートの方が到着しやすい
といった情報は提供してくれます。選挙制度のシミュレーションも同じかもしれません。
数学が、
この制度が絶対に正しい
と判断することはできません。
しかし、
この改革をすると、このような副作用が起こる可能性がある
この区割りは、他の案と比較してかなり極端である
この投票方式では、第三候補が票を分割しやすい
といった示唆は与えられます。
民主主義の制度をどの方向へ変えるかを決めるのは、最終的には人間です。数学やシミュレーションは、その判断を少しだけ明るい場所で行うための道具として使えるのではないでしょうか。
まとめ
今回は、簡易的ですが、選挙制度をPythonで簡単にシミュレーションしてみました。
確認できたのは次の3点です。
- 相対多数制では、第三候補が近い候補の票を分割し、スポイラー効果を起こす場合がある
- 同じ有権者でも、順位選択投票へ変更することで結果が大きく変わる場合がある
- 同じ総得票率でも、選挙区の区切り方によって獲得議席数は変化する
一方で、今回のモデルは現実の選挙を予測するためのものではありません。有権者の政治的位置を一次元に単純化し、戦略投票や候補者の人気、投票率、地理的制約など、多くの要素を無視しています。
それでも、
選挙制度は民意をそのまま映す鏡ではなく、民意を結果へ変換するアルゴリズムである
という点は、かなり直感的に確認できました。次に試すなら、有権者側にも「第三の山」を追加して、
第三党がどの程度の独立した支持基盤を持つと、スポイラーではなく実際の勝者になり始めるのか
をシミュレーションすると面白そうです。
また、今回の一次元モデルを二次元化して、
- 経済政策
- 社会・文化政策
のような複数軸を持たせると、講演で語られていた「有権者は本来多次元的である」という部分も、より直接的に可視化できそうです。
民主主義そのものを完全にモデル化することはできません。しかし、小さな模型を作り、条件を変え、結果を観察する。そういうアプローチで政治制度を考えてみるのも、なかなか面白いのではないでしょうか。
お疲れ様でした。
わからないところ、間違っているところ、もっといい方法がある場合は、コメントでもDMでも教えてください。

