人口の推移を簡易的にシミュレーションする

### 概要
Twitter(X)で少子高齢化について議論するうちに、少子高齢化の弊害に対する理解がやや不足している箇所があると感じ、理解促進用に簡易的なシミュレータを作ってみました。

:::note warn
注意
やや政治的な内容を含みます。
:::

### 経緯

少子高齢化対策として人権を軽視するような事をすべきと主張するツイートを見かけて、思わず以下のような引用ツイートをしてしまいました。

![スクリーンショット (155).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2641412/48e94c2d-7f6a-9dc4-405a-396391cbefbe.png)


このツイート自体は何気なく呟いたものでしたが、どういう訳かツイート主の目に留まったらしく、~~辛辣な~~反論が寄せられました。

![スクリーンショット (152).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2641412/3a25522a-6dd4-efe8-1acb-2fbd91cf868f.png)

![スクリーンショット (154).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2641412/1602ff71-8ebe-5e91-1c95-64555b29174f.png)

私自身は少子化対策そのものに反対という訳では無かったのですが、無理矢理にでも結婚・出産をさせなければ最早止める事は出来ないという主張には正直同意し難かったです。

(というか昭和の時代の価値観を今更戻すのはほぼ不可能なのでは？)

とは言え相手の主張も(倫理的には問題ですが)荒唐無稽と言う訳でもなさそうで、特に社会保障関係費の負担に関する問題は簡単に避けて通れないのも事実です。

そうして議論しているうちに

「そもそも出生率によってどの程度将来の人口が変わるの？」

「出生率の大小は社会保障費の支出額にどの程度影響があるのか、ある程度定量的に見たい！」

と気になったので、簡易的なシミュレータをPythonで作る事にしました。

### 作り方

年齢別・男女別の人口を保持する配列(リスト)を用意します。その上で

+ その年の死亡者数を死亡率から計算して、その人数を人口リストから差し引きます

+ 配列の各要素を右側にシフトします(1年経過したという体です。)

+ 合計特殊出生率の定義に従い、15歳から49歳までの女性の人口に出生率を乗じる事でその年に生まれる新生児の数を(男女比1.05:1として)計算して、その人数をリストの先頭要素とします

この操作を規定回数分繰り返す事で、簡易的ではありますが人口の推移をシミュレーションすることが出来ます(出来ているはず)。

なおシミュレーションにあたっては以下の統計情報を利用しました。

+ 日本の男女別・年齢別人口

総務省統計局[^1]のものを利用しています。
[^1]:第1表 年齢（各歳）、男女別人口及び人口性比―総人口、日本人人口（2023年10月1日現在）https://www.stat.go.jp/data/jinsui/2023np/index.html


+ 死亡率

厚生労働省[^2]のものを利用しています。
[^2]:厚生労働省 平成２０年 人口動態統計月報年計（概数）の概況
表６－１ 年齢（５歳階級）別にみた死亡数・死亡率（人口１０万対）
https://www.mhlw.go.jp/toukei/saikin/hw/jinkou/geppo/nengai08/kekka3.html


年齢層別の死亡率しか見当たらなかったので、年齢層内では死亡率が一様であると仮定して、年齢別の死亡率としています。
すなわち20~24歳の男性の死亡率が0.000581なら20歳でも24歳でも同じ死亡率であると仮定しています。

#### 社会保障費の計算

このシミュレーションは社会保障関係費の支出と社会保険料等の歳入を計算する事で収支がどのくらいになるかについても計算することが出来ます。

##### 支出

高齢者に使われる社会保障関係費については

1人当たりの額を230.4万円[^3]として、総人口のうち65歳以上の人口で掛ける事で支出を計算しています。
[^3]:市川 眞一、持続可能性に懸念高まる社会保障、ピクテ・ジャパン、2023/08/29、https://www.pictet.co.jp/investment-information/market/boost-up/20230829.html
より

##### 歳入

+ 社会保険料

+ 税金

の2要素を社会保障関係に使われる財源としています。

社会保険料については年収に依存する部分や企業が負担する分もあるため、単純に人口から計算する事が難しいです。
そのため(事業者拠出額が人口に比例するという適切ではない仮定の下)2024年度の社会保険料負担額80.3兆円[^4]を生産年齢人口約7500万人で割った107万円を1人あたりの負担額の平均値として歳入を求める事にします。
[^4]:厚生労働省、(給付と負担について2024)、https://www.mhlw.go.jp/stf/newpage_21509.html

1人あたりの負担額に生産年齢人口だけ掛ける事でその年の社会保険料の歳入を計算しています。

税金については税収のうち社会保障関係費に使われる額が約1/3である事から、1人当たりの税額68万円[^5]を3で割った22.7万円としています。
[^5]:国税庁、平均所得金額及び平均税額について(平成12年)、https://www.nta.go.jp/publication/statistics/kokuzeicho/shinkokuhyohon2000/menu/06.htm#:~:text=1%E4%BA%BA%E5%BD%93%E3%81%9F%E3%82%8A%E3%81%AE%E5%B9%B3%E5%9D%87%E7%A8%8E%E9%A1%8D,%E5%8F%8A%E3%81%B3%E7%AC%AC22%E5%9B%B3%E5%8F%82%E7%85%A7%EF%BC%89%E3%80%82


税金についてはすべての年代で負担するものとしているので、1人あたりの負担額に総人口を掛ける事で税収を求めています。

まとめると以下のような計算式になります。

```math
230.4P[\text{age}\geq65] - (107P[15\leq\text{age}<65] + 22.7P)
```

ここで$P$は人口を表し、$P[a\leq\text{age}<b]$は$a$歳以上$b$歳未満の人口を表しています。

#### その他

計算を簡単にする関係上、最高齢は100歳としています。

### 作ったもの

[Github](https://github.com/ROUND0dev/SimplePopSim)にて公開しています。非常に小規模な開発なのでこちらにもコードを掲載しておきます。

<details><summary>ソースコード</summary>

```python
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

'''
パラメータの設定値

NUM_OF_GEN:シミュレーション年数
BIRTH_RATE:出生率
PER_INSULANCE_PREMIUM:1人当たりの社会保険料負担額(万円)
PER_TAX_SSC:1人当たりの税額のうち社会保障関係費に使われる額(万円)
PER_SEN_SSC:高齢者1人当たりの社会保障給付額(万円)
'''
NUM_OF_GEN = 100
BIRTH_RATE = 1.1
PER_INSULANCE_PREMIUM = 107
PER_TAX_SSC = 22.7
PER_SEN_SSC = 230

'''
総務省統計局より公表されている
第1表 年齢（各歳）、男女別人口及び人口性比―総人口、日本人人口（2023年10月1日現在）
https://www.stat.go.jp/data/jinsui/2023np/index.html
のデータを利用

read_excelの引数にダウンロードしたエクセルファイルのパスを指定
'''
df = pd.read_excel('./05k2023-1.xlsx')

def birth(birth_rate, pop_mother):
  '''
  出生数の計算
  出生率birth_rateを35で割り、15歳から49歳までの女性の人口で掛ける事で、出生数を計算
  出生数に対して男女比1.05:1と仮定して男女別の出生数を計算
  '''
  birth_pop = birth_rate / 35 * pop_mother
  birth_pop_male = (1.05 / 2.05) * birth_pop
  birth_pop_female = birth_pop - birth_pop_male
  return birth_pop_male, birth_pop_female

def calc_ssc(pop):
  '''
  総人口popから65歳未満の人口pop_actを差し引きし、PER_COSTを乗じる事で社会保障費を計算
  '''
  pop_senior = pop[65:]
  ssc = PER_SEN_SSC * np.sum(pop_senior)
  return ssc

def calc_rev(pop):
  '''
  社会保障関係費の歳入を計算
  
  insulance_rev:社会保険料の歳入
  1人当たりの社会保険料を現役世代の人口で掛ける事で計算

  tax_rev:税金
  社会保障関係費に使われる1人当たりの税額に、成年人口を掛ける事で計算
  '''
  pop_insulance_rev = np.sum(pop[18:65])
  pop_tax_rev = np.sum(pop[18:])
  insulance_rev = PER_INSULANCE_PREMIUM * pop_insulance_rev
  tax_rev = PER_TAX_SSC * pop_tax_rev
  return insulance_rev + tax_rev

def step(params):
  '''
  1年間の人口増減シミュレーション
  pop_m:男性の年齢別人口(0~99歳)
  pop_f:女性の年齢別人口(0~99歳)
  rate_death_m:男性の年齢別死亡率
  rate_death_f:女性の年齢別死亡率
  birth_m:男性の出生数
  birth_f:女性の出生数
  pop_next_m:1年先の男性の年齢別人口
  pop_next_f:1年先の女性の年齢別人口
  '''
  pop_m = params['pop_male']
  pop_f = params['pop_female']
  rate_death_m = params['rate_death_male']
  rate_death_f = params['rate_death_female']
  birth_m = params['birth_male']
  birth_f = params['birth_female']

  death_m = pop_m * rate_death_m
  death_f = pop_f * rate_death_f

  pop_m -= death_m
  pop_f -= death_f

  pop_next_m = np.roll(pop_m, 1)
  pop_next_f = np.roll(pop_f, 1)

  pop_next_m[0] = birth_m
  pop_next_f[0] = birth_f
  
  return pop_next_m, pop_next_f

def create_rate_death(male_5s, female_5s):
  '''
  年齢層別(5年ごと)の死亡率を年齢別死亡率に変換
  '''
  
  rate_death_male = []
  rate_death_female = []
  for i in range(100):
    rate_death_male.append(male_5s[int(i / 5)])
    rate_death_female.append(female_5s[int(i / 5)])
    
  rate_death_male[-1] = male_5s[-1]
  rate_death_female[-1] = female_5s[-1]

  return rate_death_male, rate_death_female

def simulate_ssc(**kwargs):
  '''
  人口推移+社会保障費の推移をシミュレーション
  '''
  
  init_pop_male = kwargs.get('init_pop_male')
  init_pop_female = kwargs.get('init_pop_female')
  rate_death_male = kwargs.get('rate_death_male')
  rate_death_female = kwargs.get('rate_death_female')
  pop_list = kwargs.get('pop_list')
  pop_list_male = kwargs.get('pop_list_male')
  pop_list_female = kwargs.get('pop_list_female')
  ssc_list = kwargs.get('ssc_list')
  rev_list = kwargs.get('rev_list')
  birth_pop_list = kwargs.get('birth_pop_list')

  pop_m = [ float(e) for e in init_pop_male ]
  pop_f = [ float(e) for e in init_pop_female ]

  for i in range(NUM_OF_GEN):
    np_pop_m = np.array(pop_m)
    np_pop_f = np.array(pop_f)
    
    '''
    合計特殊出生率の定義に従って出生数を計算するため、15歳から49歳までの女性の人口を求めている
    '''
    pop_mother = np.sum(np_pop_f[15:50])
    birth_male, birth_female = birth(BIRTH_RATE, pop_mother)
    
    params = { 'pop_male': np_pop_m, 'pop_female': np_pop_f, 'rate_death_male': rate_death_male, 'rate_death_female': rate_death_female,
              'birth_male': birth_male, 'birth_female': birth_female}
    pop_m, pop_f = step(params)

    pop = pop_m + pop_f
    pop_list.append(list(pop))
    pop_list_male.append(list(pop_m))
    pop_list_female.append(list(pop_f))
    ssc = calc_ssc(pop)
    rev = calc_rev(pop)
    ssc_list.append(ssc)
    rev_list.append(rev)

    birth_pop_list.append(birth_male + birth_female)

def main():
  '''
  エクセルファイルから初期値を設定
  '''
  init_pop_male_l = df.iloc[12:62, 2].tolist()
  init_pop_male_u = df.iloc[12:62, 11].tolist()
  init_pop_male_l.extend(init_pop_male_u)

  init_pop_female_l = df.iloc[12:62, 3].tolist()
  init_pop_female_u = df.iloc[12:62, 12].tolist()
  init_pop_female_l.extend(init_pop_female_u)

  init_pop_male = init_pop_male_l
  init_pop_female = init_pop_female_l

  '''
  年齢層ごとの10万人あたりの死亡率
  厚生労働省 平成２０年 人口動態統計月報年計（概数）の概況
  表６－１ 年齢（５歳階級）別にみた死亡数・死亡率（人口１０万対）
  https://www.mhlw.go.jp/toukei/saikin/hw/jinkou/geppo/nengai08/kekka3.html
  より
  '''
  rate_death_male_5s = [0.000735, 0.000108, 0.000109, 0.000341, 0.000581, 0.000649, 0.000773, 0.001039, 0.001577, 0.002513, 0.004049, 0.006587, 0.009775, 0.014555, 0.023981, 0.041697, 0.070413, 0.118574, 0.196260, 0.283818, 1.0]
  rate_death_female_5s = [0.000664, 0.000086, 0.000064, 0.000189, 0.000277, 0.000325, 0.000423, 0.000587, 0.000847, 0.001310, 0.001986, 0.002905, 0.004014, 0.005947, 0.010479, 0.018820, 0.035870, 0.070219, 0.131947, 0.212797, 1.0]
  
  '''
  年齢層別の人口となっているので、年齢別の人口に変換する
  '''
  rate_death_male, rate_death_female = create_rate_death(rate_death_male_5s, rate_death_female_5s)

  ssc_list = []
  rev_list = []
  birth_pop_list = [ ]
  pop_list = [ init_pop_male + init_pop_female ]
  pop_list_male = [ init_pop_male ]
  pop_list_female = [ init_pop_female ]
  simulate_ssc(init_pop_male=init_pop_male, init_pop_female=init_pop_female, rate_death_male=rate_death_male
               ,rate_death_female=rate_death_female, pop_list=pop_list, pop_list_male=pop_list_male, pop_list_female=pop_list_female
               ,ssc_list=ssc_list, rev_list=rev_list, birth_pop_list=birth_pop_list)
  
  '''
  人口の推移をグラフで表示
  '''
  x = np.linspace(0, NUM_OF_GEN, NUM_OF_GEN)
  summarize_pop = [ sum(pop_list[i]) for i in range(NUM_OF_GEN) ]
  plt.xlabel("year")
  plt.ylabel("population")
  plt.plot(x, summarize_pop, label="population changes")
  plt.legend()
  plt.show()

  '''
  社会保障費の歳入と支出額の推移をグラフで表示
  '''
  plt.xlabel("year")
  plt.ylabel("amount")
  plt.plot(x, ssc_list, label="ssc")
  plt.plot(x, rev_list, label="rev")
  plt.legend()
  plt.show()

if __name__ == '__main__':
  main()
```

</details>

### 動かしてみる

まず出生率を1.1、世代数を100にしてみると以下のようになります。

![pop_after100.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2641412/47b68b66-216c-21d4-a4db-2fb1d47ac9b5.png)

縦軸は(×1000人)です。
2120年には3000万人程度になってしまうとは…。
ちょっと減りすぎな気もしますが国立社会保障人口問題研究所の2020年度の予測によれば2120年には3600万人(出生率1.12の場合)としているので概ね合致してそうです。

人口維持ラインの出生率が2.05~2.07とされているので、2.1に設定してみてシミュレーションしてみるとこんな感じになります。

![pop_after100_r21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2641412/6df3d166-89a7-25b0-7703-ff2d0aae0418.png)

このままだと差異がよく分からないので2つのグラフを同時に描画すると

![pop_after100_comp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2641412/5e2d7c04-3b17-6836-42f5-5d3944cedc15.png)

大分差がありますね…。

ちなみに社会保障関係費の方はこんな感じになります。

![ssc_rev_comp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2641412/c580ca84-066a-da18-9dfa-cf486c9d3f6b.png)

このグラフでは$支出-歳入$とした差分の比較になります。

縦軸は百億円単位(×10,000,000,000)になります。
マイナスなら黒字、プラスなら赤字です。

設定した値がかなり漠然としたものなので信憑性には欠けますが、人口を増やさなければ赤字垂れ流しになってしまうのは間違いないようです。

### 結論

人口推移の方はそれなりにきちんとシミュレーション出来ていそうです。

また人口減少による弊害が大きい事は分かりました。

とは言え独身の方に対して結婚するように圧力を掛けたり、女性に産む事を強いる社会は果たして優れているのでしょうか？

理系大学院生、かつ(IT)エンジニアを志す人間としてはそんな社会にならないように、科学技術の力でスマートに解決したいと考えています。

長くなりましたが、最後までお読みいただきありがとうございました。