システムとモデリング

modelica, Julia, Design Structure Matrix, SysML, 他モデリング全般について。

所得に応じて票の重みが変わったら、2025参院選の結果はどうなるのか?【加重投票シミュレーション】

選挙において「一人一票」は平等の原則の根幹です。しかし、仮に「納税額」や「所得」によって票の重みが変わるとしたら、選挙結果はどうなるのでしょうか?

今回は、年齢層ごとの所得と政党投票率をもとに、票の重みを加味した選挙シミュレーションをJuliaで実行しました。


データの準備

もとにしたのは以下の3種類のデータです:

  • 年齢層ごとの人口 政府統計の人口推計データを使用しました。

www.e-stat.go.jp

  • 年齢層ごとの平均所得国税庁のデータをベース)

https://www.nta.go.jp/publication/statistics/kokuzeicho/minkan2023/pdf/R05_12.pdf

www.ntv.co.jp

これらのデータを元に、以下のような年齢ごとに投票先政党割合・年収・人口テーブルを作成しました。

年齢層 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つのケースを比較しました:

  1. 通常の「一人一票制」
  2. 所得に比例した「加重投票制」

それぞれにおいて、各年齢層の政党支持率を人口で加重し、さらに加重投票では平均所得を票の重みとして乗じました。

小政党は「その他」に統合

見やすい可視化のために、得票率が5%未満の政党は「その他」としてまとめました。


結果(パイチャート)

以下は Julia + CairoMakie によって作成したパイチャートです:

  • 左:一人一票制
  • 右:所得比例制

所得により一票の重みが変わる場合、比較的所得の少ない若年層と高齢者のウェイトが小さくなり、反対に所得の多い40代・50代の影響が大きくなります。 ただし、もともと40代50代が人口ボリュームゾーンでもありましたので、所得比例制の結果と一人一票制の結果は大きく変わりませんでした。 今回は所得と支持政党のデータが無かったため年齢層毎に所得をまとめてしまっていますが、同じ年齢層でも所得により支持政党が異なる場合はもっと違った結果になると思われます。

また、今回の一人一票制の結果は日本テレビ出口調査の結果でしかなく、実際の選挙結果は異なります。実際の選挙結果はNHKのサイトにまとまっています

www.nhk.or.jp

今回のシミュレーションは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