選挙において「一人一票」は平等の原則の根幹です。しかし、仮に「納税額」や「所得」によって票の重みが変わるとしたら、選挙結果はどうなるのでしょうか?
今回は、年齢層ごとの所得と政党投票率をもとに、票の重みを加味した選挙シミュレーションをJuliaで実行しました。
データの準備
もとにしたのは以下の3種類のデータです:
- 年齢層ごとの人口 政府統計の人口推計データを使用しました。
- 年齢層ごとの平均所得(国税庁のデータをベース)
https://www.nta.go.jp/publication/statistics/kokuzeicho/minkan2023/pdf/R05_12.pdf
これらのデータを元に、以下のような年齢ごとに投票先政党割合・年収・人口テーブルを作成しました。
年齢層 | 18-19 | 20-29 | 30-39 | 40-49 | 50-59 | 60-69 | 70- |
---|---|---|---|---|---|---|---|
自由民主党 | 0.134 | 0.113 | 0.126 | 0.161 | 0.206 | 0.263 | 0.369 |
公明党 | 0.036 | 0.034 | 0.039 | 0.046 | 0.055 | 0.078 | 0.087 |
立憲民主党 | 0.081 | 0.072 | 0.074 | 0.092 | 0.118 | 0.158 | 0.199 |
日本維新の会 | 0.049 | 0.048 | 0.058 | 0.074 | 0.075 | 0.068 | 0.058 |
国民民主党 | 0.234 | 0.247 | 0.187 | 0.142 | 0.117 | 0.088 | 0.057 |
れいわ新選組 | 0.063 | 0.064 | 0.086 | 0.116 | 0.107 | 0.065 | 0.027 |
日本共産党 | 0.033 | 0.031 | 0.025 | 0.03 | 0.032 | 0.044 | 0.07 |
参政党 | 0.232 | 0.242 | 0.232 | 0.187 | 0.153 | 0.116 | 0.055 |
日本保守党 | 0.052 | 0.066 | 0.068 | 0.058 | 0.052 | 0.042 | 0.019 |
社会民主党 | 0.009 | 0.009 | 0.01 | 0.011 | 0.016 | 0.026 | 0.029 |
NHK党 | 0.011 | 0.009 | 0.013 | 0.014 | 0.014 | 0.011 | 0.005 |
再生の道 | 0.01 | 0.008 | 0.011 | 0.012 | 0.01 | 0.009 | 0.005 |
チームみらい | 0.034 | 0.038 | 0.05 | 0.03 | 0.018 | 0.011 | 0.004 |
日本改革党 | 0.002 | 0.001 | 0.001 | 0.001 | 0.001 | 0.001 | 0.001 |
日本誠真会 | 0.004 | 0.003 | 0.005 | 0.008 | 0.01 | 0.008 | 0.005 |
無所属連合 | 0.008 | 0.006 | 0.009 | 0.011 | 0.007 | 0.006 | 0.004 |
人口 | 2,127,600 | 11,563,000 | 12,407,000 | 15,753,000 | 18,010,000 | 14,640,000 | 29,391,000 |
平均年収 | 1,124,000 | 3,468,834 | 4,499,000 | 5,119,700 | 5,420,100 | 4,102,100 | 2,930,000 |
シミュレーションの方法
以下2つのケースを比較しました:
- 通常の「一人一票制」
- 所得に比例した「加重投票制」
それぞれにおいて、各年齢層の政党支持率を人口で加重し、さらに加重投票では平均所得を票の重みとして乗じました。
小政党は「その他」に統合
見やすい可視化のために、得票率が5%未満の政党は「その他」としてまとめました。
結果(パイチャート)
以下は Julia + CairoMakie によって作成したパイチャートです:
- 左:一人一票制
- 右:所得比例制
所得により一票の重みが変わる場合、比較的所得の少ない若年層と高齢者のウェイトが小さくなり、反対に所得の多い40代・50代の影響が大きくなります。 ただし、もともと40代50代が人口ボリュームゾーンでもありましたので、所得比例制の結果と一人一票制の結果は大きく変わりませんでした。 今回は所得と支持政党のデータが無かったため年齢層毎に所得をまとめてしまっていますが、同じ年齢層でも所得により支持政党が異なる場合はもっと違った結果になると思われます。
また、今回の一人一票制の結果は日本テレビの出口調査の結果でしかなく、実際の選挙結果は異なります。実際の選挙結果はNHKのサイトにまとまっています
今回のシミュレーションは1つの思考実験に過ぎませんが、「誰の意見がより反映されるべきか?」という民主主義の根本を問い直す材料にもなり得ます。
使用技術と構成
- 言語:Julia
- 描画:CairoMakie
- 処理:
CSV.read()
でデータ読み込み- 年齢層別辞書でデータ構造化
vote()
関数で加重得票計算merge_small_parties()
で小政党を「その他」に統合pie_chart!()
関数で可視化
おわりに
今回の分析では、「所得に応じた票の重みづけ」を行うことで、政党の得票構成がどのように変わるかを確認しました。
もちろん、実際にこうした制度を導入するには様々な問題があります。しかし、数理的に想定される影響を“見える化”することは、政治参加や制度設計を考えるうえで非常に有意義です。
今後は、投票率の世代差や地域別分析なども取り入れた発展型シミュレーションにも挑戦したいと思います
使用したJuliaコード
using CairoMakie, CSV, DataFrames # === CSVファイル読み込み === df = CSV.read("得票率データ.csv", DataFrame) # === データ整形:各年齢層ごとの人口・年収・支持率 === function build_dicts(df) population = Dict() income = Dict() support_rate = Dict() exclude = ["年齢層", "人口", "平均年収"] party_cols = filter(c -> !(c in exclude), names(df)) for row in eachrow(df) age = row.年齢層 population[age] = row.人口 / 1_000_000 income[age] = row.平均年収 / 10_000 rates = Dict{String, Float64}() for p in party_cols rates[string(p)] = coalesce(row[p], 0.0) end support_rate[age] = rates end return population, income, support_rate end # === 投票集計(人口 × 支持率 × 所得) === function vote(population, support_rate; income=nothing) all_parties = unique(vcat([collect(keys(sr)) for sr in values(support_rate)]...)) result = Dict(p => 0.0 for p in all_parties) for age in keys(population) weight = isnothing(income) ? 1.0 : income[age] pop = population[age] for party in all_parties result[party] += support_rate[age][party] * pop * weight end end return result end # === 得票率を百分率化 === function vote_share(result) total = sum(values(result)) Dict(k => round(v / total * 100; digits=2) for (k, v) in result) end # === 小政党を「その他」に統合する === function merge_small_parties(share::Dict{String, Float64}; threshold=5.0) merged = Dict("その他" => 0.0) for (party, rate) in share if rate < threshold merged["その他"] += rate else merged[party] = rate end end return merged end # === パイチャート描画 === function pie_chart!(ax, values, labels, colors) total = sum(values) start_angle = 0.0 for (i, v) in enumerate(values) frac = v / total angle_span = 2π * frac end_angle = start_angle + angle_span sector = [Point2f(0, 0)] for θ in range(start_angle, end_angle, length=50) push!(sector, Point2f(cos(θ), sin(θ))) end push!(sector, Point2f(0, 0)) poly!(ax, sector, color=colors[i]) θ = start_angle + angle_span / 2 x, y = 0.75 * cos(θ), 0.75 * sin(θ) text!(ax, "$(labels[i]): $(round(v, digits=1))%", position=(x, y), align=(:center, :center), fontsize=16) start_angle = end_angle end end # === 実行 === population, income, support_rate = build_dicts(df) equal_share = vote_share(vote(population, support_rate)) weighted_share = vote_share(vote(population, support_rate, income=income)) # === 小政党を「その他」にまとめる === equal_share = merge_small_parties(equal_share; threshold=5.0) weighted_share = merge_small_parties(weighted_share; threshold=5.0) # === ラベルと値 === labels = collect(keys(equal_share)) equal_votes = [equal_share[p] for p in labels] weighted_votes = [weighted_share[p] for p in labels] # === 色設定(「その他」は常に灰色) === colors = [ :dodgerblue, :limegreen, :crimson, :orange, :orchid, :deepskyblue4, :goldenrod, :turquoise, :slateblue, :darkgreen, :coral, :navy, :magenta, :firebrick, :mediumseagreen ] # 必要数だけ取り出し、最後が「その他」ならグレーを追加 c_used = colors[1:length(labels)] for (i, lab) in enumerate(labels) if lab == "その他" c_used[i] = :gray end end # === グラフ描画 === fig = Figure(size=(1000, 450)) ax1 = Axis(fig[1, 1], title="一人一票制", aspect=1) hidedecorations!(ax1); hidespines!(ax1) pie_chart!(ax1, equal_votes, labels, c_used) ax2 = Axis(fig[1, 2], title="所得比例制", aspect=1) hidedecorations!(ax2); hidespines!(ax2) pie_chart!(ax2, weighted_votes, labels, c_used) fig