人口の予測

# 概要

統計関係の定番ネタ、将来人口推計です。  
百年先までの日本の人口の推移を予測します。

# 準備

まず、ライブラリパッケージを読み込んで、データを用意します。  
そして必要な数値の計算も行います。

```python
# --- ライブラリ読み込み ---
import numpy as np
import pandas as pd

import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
plt.rcParams['font.family'] = 'Yu Mincho'

# --- データ ---
# 年齢5歳階級別女性人口・有配偶女性人口：2015年（出典：統計局・国勢調査）、
# 年齢5歳階級別母の年齢別出生数：2016年（出典：統計局・人口動態調査）
dfBorn = pd.DataFrame({
    '年齢区分':['15～19歳', '20～24歳', '25～29歳', '30～34歳', '35～39歳', '40～44歳', '45～49歳'],
    '女性人口':[2922972, 2921735, 3153895, 3606131, 4111955, 4818200, 4307927],
    '有配偶女性':[14859, 224043, 1096784, 2134029, 2806343, 3395079, 3078307],
    '母の年齢別出生数':[11053, 82194, 250715, 355018, 223329, 53484, 1350],
})

# 性別・年齢5歳階級別人口（出典：統計局・国勢調査）
dfPopl = pd.DataFrame({
    '年齢区分':['0～4歳', '5～9歳', '10～14歳', '15～19歳', '20～24歳', '25～29歳', '30～34歳',
       '35～39歳', '40～44歳', '45～49歳', '50～54歳', '55～59歳', '60～64歳',
       '65～69歳', '70～74歳', '75～79歳', '80～84歳', '85～89歳', '90～94歳',
       '95～99歳', '100歳以上'],
    '男2010年':[2710581, 2859805, 3031943, 3109229, 3266240, 3691723, 4221011,
       4950122, 4400375, 4027969, 3809576, 4287489, 4920468, 3921774,
       3225503, 2582940, 1692584,  744222,  241799,   55739,    5851],
    '女2010年':[2586167, 2725856, 2889092, 2954128, 3160193, 3601978, 4120486,
       4836227, 4341490, 4005147, 3834923, 4376245, 5116781, 4288399,
       3737799, 3358073, 2643680, 1688366,  779908,  241017,   38031],
    '男2015年':[2550921, 2714591, 2868024, 3085416, 3046392, 3255717, 3684747,
       4204202, 4914018, 4354877, 3968311, 3729523, 4151119, 4659662,
       3582440, 2787417, 1994326, 1056641,  333335,   63265,    8383],
    '女2015年':[2436785, 2585196, 2731293, 2922972, 2921735, 3153895, 3606131,
       4111955, 4818200, 4307927, 3961985, 3785723, 4303891, 4984205,
       4113371, 3489439, 2967094, 2060616, 1015785,  296082,   53380],
    '男2020年':[2311189, 2606651, 2742131, 2880029, 3017869, 3074087, 3297031,
       3696855, 4189446, 4862990, 4277003, 3865303, 3592903, 3910060,
       4249286, 3092860, 2196093, 1303473,  491303,   92110,    9766],
    '女2020年':[2204893, 2482442, 2608386, 2737411, 2913437, 2957877, 3187563,
       3614712, 4101631, 4787303, 4262848, 3902179, 3704287, 4165208,
       4762509, 3838068, 3100635, 2366350, 1287713,  400614,   69757],
})


# --- 必要な数値の計算 ---
# 有配偶割合・出生率・有配偶出生率を計算
dfBorn['有配偶割合'] = dfBorn['有配偶女性']/dfBorn['女性人口']
dfBorn['年齢別出生率'] = dfBorn['母の年齢別出生数']/dfBorn['女性人口']
dfBorn['年齢別有配偶出生率'] = dfBorn['母の年齢別出生数']/dfBorn['有配偶女性']

# 年齢5歳階級別の5年間での減少率を計算
dfPopl['男増減率'] = 0.
dfPopl['女増減率'] = 0.

Year0 = str(2010) # 前回
Year1 = str(2015) # 今回

L = dfPopl.shape[0] # 行数
for i in range(L-1):
    Y = dfPopl.at[i+1,'年齢区分']
    if Y!='100歳以上':
        # 100歳未満
        dfPopl.at[i+1,'男増減率'] = np.round(dfPopl.at[i+1,'男'+Year1+'年'] / dfPopl.at[i,'男'+Year0+'年'], 4)
        dfPopl.at[i+1,'女増減率'] = np.round(dfPopl.at[i+1,'女'+Year1+'年'] / dfPopl.at[i,'女'+Year0+'年'], 4)
        
    else:
        # 100歳以上
        dfPopl.at[i+1,'男増減率'] = np.round( dfPopl.at[i+1,'男'+Year1+'年'] / (dfPopl.at[i,'男'+Year0+'年'] + dfPopl.at[i+1,'男'+Year0+'年']), 4)
        dfPopl.at[i+1,'女増減率'] = np.round( dfPopl.at[i+1,'女'+Year1+'年'] / (dfPopl.at[i,'女'+Year0+'年'] + dfPopl.at[i+1,'女'+Year0+'年']), 4)

# 過去15年分の実際の人口の計算
dfReal = pd.DataFrame(columns=['年','総人口'], index=range(3))
for i,y in enumerate([2010,2015,2020]):
    dfReal.at[i,'年'] = y
    Year = str(y)
    dfReal.at[i,'総人口'] =  dfPopl['男'+Year+'年'].sum() + dfPopl['女'+Year+'年'].sum()

```

# 100年先までの予測

では、準備した数値を使って、100年先までの5年ごとの人口の予測値を計算します。  
計算としては、男女別の5歳分の人口にそれぞれの減少率をかけて5年後の予測値を求める一方で、
5歳未満の人口を有配偶女性人口と有配偶出生率から求め、
それを集計するということを、5年ごとに繰り返して集計しています。

```python
# --- 100年先までの予測 ---

Ys = dfBorn['年齢区分'].values                   # 出生率関連の年齢階級
year_list = [y for y in range(2020, 2125, 5)]    # 5年置きに100年後まで

dfResult1 = pd.DataFrame(columns=['年', '予測人口'], index=range(len(year_list)))
dfEst = dfPopl.copy()
for j, year in enumerate(year_list):
    # === 準備 ===
    Year1 = str(year)          # 予測したい年
    Year0 = str(year-5)        # その5年前
    dfEst['男'+Year1+'年'] = 0 # 予測用列の作成
    dfEst['女'+Year1+'年'] = 0 # 予測用列の作成

    # === 出生数の計算 ===
    NofB = 0
    for Y in Ys:
        NofL = dfEst.loc[dfEst['年齢区分']==Y, '女'+Year0+'年'].values[0]       # その年代の女性の数
        rate1 = dfBorn.loc[dfBorn['年齢区分']==Y, '有配偶割合'].values[0]        # その年代の有配偶割合
        rate2 = dfBorn.loc[dfBorn['年齢区分']==Y, '年齢別有配偶出生率'].values[0] # その年代の年齢別有配偶出生率
        NofB += NofL * rate1 * rate2 * 5. # 5年分の出生数

    dfEst.at[0,'男'+Year1+'年'] = np.round(NofB *0.512) # 男児の出生数
    dfEst.at[0,'女'+Year1+'年'] = np.round(NofB *0.488) # 女児の出生数

    # === 増減分の計算 ===
    L = dfPopl.shape[0] # 行数
    for i in range(L-1):
        Y = dfEst.at[i+1,'年齢区分']
        if Y!='100歳以上':
            # 100歳以上ではない場合
            dfEst.at[i+1,'男'+Year1+'年'] = dfEst.at[i,'男'+Year0+'年'] * dfPopl.at[i+1,'男増減率']
            dfEst.at[i+1,'女'+Year1+'年'] = dfEst.at[i,'女'+Year0+'年'] * dfPopl.at[i+1,'女増減率']

        else:
            # 100歳以上の場合
            dfEst.at[i+1,'男'+Year1+'年'] = (dfEst.at[i+1,'男'+Year0+'年'] + dfEst.at[i,'男'+Year0+'年']) *  dfPopl.at[i+1,'男増減率']
            dfEst.at[i+1,'女'+Year1+'年'] = (dfEst.at[i+1,'女'+Year0+'年'] + dfEst.at[i,'女'+Year0+'年']) *  dfPopl.at[i+1,'女増減率']
        
    # === 結果の記録 ===
    dfResult1.at[j,'年'] = year
    dfResult1.at[j,'予測人口'] =  np.round( dfEst['男'+Year1+'年'].sum()+dfEst['女'+Year1+'年'].sum() )

dfEst1 = dfEst.copy()

# --- グラフ表示 ---
fig = plt.figure(facecolor='w', figsize=(8,4))
ax = fig.add_subplot(111)
ax.plot(dfResult1['年'], dfResult1['予測人口']/10000, marker='o', label='予測', alpha=0.4, zorder=5)
ax.plot(dfReal['年'], dfReal['総人口']/10000, marker='o', label='実際', alpha=0.8, zorder=3)
ax.set(ylim=(0,13000), ylabel='人口[万人]', xlabel='年', title='今後100年の日本の人口の予測')
ax.grid(zorder=1)
ax.legend(loc='lower left', fontsize=16)
plt.show()

```

![今後100年の人口.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2706917/9b65e9b4-cbd0-cf65-92b5-49012e6d3c05.png)

2020年の人口がきちんと予測できていて、計算の正しさが証明されています。  
そして、（各種数値が2015年ごろのままなら）100年で人口が3分の1に落ち込み、さらにそのままの勢いで減少を続けるという、厳しい未来が見えました。

# 動画作成

せっかく5年ごとの性別・5歳階級別人口が計算されているので、こちらもグラフ出力しましょう。  
一枚ずつ見るのも大変なので、動画での出力（png形式）にしています。

```python
# --- 動画pngの作成 ---
Year = str(2015)
dfEst1s = dfEst1.loc[4:11,:] # 20歳から59歳までを取り出す

total = np.round( (dfEst1['男'+Year+'年'].sum()+dfEst1['女'+Year+'年'].sum()) / 10000, 1 )

fig = plt.figure(facecolor='w', figsize=(16,10))
ax1 = fig.add_subplot(211)
ax2 = fig.add_subplot(212)
ax1.bar(dfEst1['年齢区分'], dfEst1['男'+Year+'年']/10000, zorder=5, color='cornflowerblue', alpha=0.7)
ax2.bar(dfEst1['年齢区分'], dfEst1['女'+Year+'年']/10000, zorder=5, color='coral', alpha=0.7)
ax1.bar(dfEst1s['年齢区分'], dfEst1s['男'+Year+'年']/10000, zorder=6, color='blue') # 20歳～59歳を別の色で上書き
ax2.bar(dfEst1s['年齢区分'], dfEst1s['女'+Year+'年']/10000, zorder=6, color='red') # 20歳～59歳を別の色で上書き
ax1.text(0.1, 400, Year+'年 男性', size=24)
ax2.text(0.1, 400, Year+'年 女性', size=24)
ax1.text(15, 400, '総人口： '+str(total)+'万人', size=24)
ax1.set(ylim=(0,500), ylabel='人口[万人]', xticklabels=[], title='今後100年の人口変動の予測')
ax2.set(ylim=(0,500), ylabel='人口[万人]', xlabel='年')
ax1.grid(zorder=1)
ax2.grid(zorder=1)
plt.xticks(rotation=90)

def update(f):
    ax1.cla()
    ax2.cla()
    Year = str(f)
    total = np.round( (dfEst1['男'+Year+'年'].sum()+dfEst1['女'+Year+'年'].sum()) / 10000, 1 )
    
    ax1.bar(dfEst1['年齢区分'], dfEst1['男'+Year+'年']/10000, zorder=5, color='cornflowerblue', alpha=0.7)
    ax2.bar(dfEst1['年齢区分'], dfEst1['女'+Year+'年']/10000, zorder=5, color='coral', alpha=0.7)
    ax1.bar(dfEst1s['年齢区分'], dfEst1s['男'+Year+'年']/10000, zorder=6, color='blue')
    ax2.bar(dfEst1s['年齢区分'], dfEst1s['女'+Year+'年']/10000, zorder=6, color='red')
    ax1.text(0.1, 400, Year+'年 男性', size=24)
    ax2.text(0.1, 400, Year+'年 女性', size=24)
    ax1.text(15, 400, '総人口： '+str(total)+'万人', size=24)
    ax1.set(ylim=(0,500), ylabel='人口[万人]', xticklabels=[], title='今後100年の人口変動の予測')
    ax2.set(ylim=(0,500), ylabel='人口[万人]', xlabel='年')
    ax1.grid(zorder=1)
    ax2.grid(zorder=1)
    plt.xticks(rotation=90)

anime = FuncAnimation(fig, update, frames=year_list, interval=500)
anime.save('population01.png', writer='pillow')

plt.close()

```

![population01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2706917/bb79ad5a-d6bd-6879-a5a2-6b157dc4dc0e.png)

（png動画が再生されない場合は、画像をクリックしてみてください）

人口が擦り減っていく様がありありと分かる動画です。


# IFの世界

減っていく一方の人口に暗澹たる気持ちになったところで、救いを探してみました。  

実は今回のコードでは（よく見て頂ければわかりますが）年齢階級別の出生率を、有配偶出生率と女性の有配偶割合に分解しています。  
もちろん、分解してもしなくても計算結果は同じです。

ではなぜ、そうしたかといえば、女性の婚姻年齢がもし変動したらどうなるのかを見たかったからです。  
実際日本では、98%の子供は婚姻している女性から生まれます（出典：統計局・2019年人口動態調査結果）。  
婚姻こそが、出生の重大な鍵となっているのです。  

以下は「もしも女性の婚姻年齢が2040年ごろに急に5年早まったら」、という「IF」の計算です。  
（2040年がきたら25歳～34歳の部分の有配偶割合を20歳～29歳のところに上書きするという雑なやり方ですが）

```python
# --- 100年先までの予測（女性の有配偶割合が5歳前倒しするIF） ---

dfBorn2 = dfBorn.copy() # 上書き防止
YearX = 2040            # 前倒しが起きる年

Ys = dfBorn2['年齢区分'].values                        # 出生率関連の年齢階級
year_list = [y for y in range(2020, 2125,5)]          # 5年置きに100年後まで

dfResult2 = pd.DataFrame(columns=['年', '予測人口'], index=range(len(year_list)))
dfEst = dfPopl.copy()
for j, year in enumerate(year_list):
    # === 準備 ===
    Year1 = str(year)          # 予測したい年
    Year0 = str(year-5)        # その5年前
    dfEst['男'+Year1+'年'] = 0 # 予測用列の作成
    dfEst['女'+Year1+'年'] = 0 # 予測用列の作成

    if year==YearX:
        # YearX年に、有配偶割合が5年前倒しされた場合
        dfBorn2.loc[dfBorn2['年齢区分']=='20～24歳','有配偶割合'] = dfBorn.loc[dfBorn['年齢区分']=='25～29歳','有配偶割合'].values[0]
        dfBorn2.loc[dfBorn2['年齢区分']=='25～29歳','有配偶割合'] = dfBorn.loc[dfBorn['年齢区分']=='30～34歳','有配偶割合'].values[0]

    # === 出生数の計算 ===
    NofB = 0
    for Y in Ys:
        NofL = dfEst.loc[dfEst['年齢区分']==Y, '女'+Year0+'年'].values[0]          # その年代の女性の数
        rate1 = dfBorn2.loc[dfBorn2['年齢区分']==Y, '有配偶割合'].values[0]        # その年代の有配偶割合
        rate2 = dfBorn2.loc[dfBorn2['年齢区分']==Y, '年齢別有配偶出生率'].values[0]# その年代の年齢別有配偶出生率
        NofB += NofL * rate1 * rate2 * 5. # 5年分の出生数

    dfEst.at[0,'男'+Year1+'年'] = np.round(NofB *0.512) # 男児の出生数
    dfEst.at[0,'女'+Year1+'年'] = np.round(NofB *0.488) # 女児の出生数

    # === 増減分の計算 ===
    L = dfPopl.shape[0] # 行数
    for i in range(L-1):
        Y = dfEst.at[i+1,'年齢区分']
        if Y!='100歳以上':
            # 100歳以上ではない場合
            dfEst.at[i+1,'男'+Year1+'年'] = dfEst.at[i,'男'+Year0+'年'] * dfPopl.at[i+1,'男増減率']
            dfEst.at[i+1,'女'+Year1+'年'] = dfEst.at[i,'女'+Year0+'年'] * dfPopl.at[i+1,'女増減率']

        else:
            # 100歳以上の場合
            dfEst.at[i+1,'男'+Year1+'年'] = (dfEst.at[i+1,'男'+Year0+'年'] + dfEst.at[i,'男'+Year0+'年']) *  dfPopl.at[i+1,'男増減率']
            dfEst.at[i+1,'女'+Year1+'年'] = (dfEst.at[i+1,'女'+Year0+'年'] + dfEst.at[i,'女'+Year0+'年']) *  dfPopl.at[i+1,'女増減率']
        
    # === 結果の記録 ===
    dfResult2.at[j,'年'] = year
    dfResult2.at[j,'予測人口'] =  np.round( dfEst['男'+Year1+'年'].sum()+dfEst['女'+Year1+'年'].sum() )
    
dfEst2 = dfEst.copy()

# --- グラフ表示 ---
fig = plt.figure(facecolor='w', figsize=(8,4))
ax = fig.add_subplot(111)
ax.plot(dfResult2['年'], dfResult2['予測人口']/10000, marker='o', label='予測', alpha=0.4, zorder=5)
ax.plot(dfReal['年'], dfReal['総人口']/10000, marker='o', label='実際', alpha=0.8, zorder=3)
ax.set(ylim=(0,13000), ylabel='人口[万人]', xlabel='年', title='【IF版】今後100年の日本の人口の予測')
ax.grid(zorder=1)
ax.legend(loc='lower left', fontsize=16)
plt.show()
```

![【IF版】今後100年の人口.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2706917/6cc206d3-fdd2-ff0b-3812-5bad63724f71.png)

いいですね。
8000万人強でなんとか下げ止まります（それでも3割減ですが止まらないよりましです）。

動画出力は、（ほぼ同じコードなので）コードを省略して結果だけ載せます。

![population02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2706917/56fdad9a-d53a-76f8-794f-448cc88527df.png)

（png動画が再生されない場合は、画像をクリックしてみてください）


# 考察

女性の婚姻経験の割合は、次のグラフの通りになっています（数値の出典：統計局・国勢調査、有配偶・離婚・死別・不明を合計したものを非未婚者として計算しました）。

![女性の婚姻割合.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2706917/c1f4800b-5856-71a0-44f1-40636a257990.png)

グラフの通り、1980年当時の皆婚社会と違い、現在では約2割の女性は生涯結婚しません。  
でも、8割の方は結婚します。  

今回の「IF」は「結婚する側の8割の女性がもし結婚の決断を5年早めるとしたら？」というシナリオを考えたことになります。

出生動向基本調査によると、結婚した女性の約8割は20代のうちに夫となる人と出会っています。  
これはこの15年ほどの間で変わっていません。  
つまり、出会ってそれほど時間をおかずに結婚に至るなら、このシナリオは実現可能です。

もし、結婚を先延ばしにしないといけない事情が多くの人にあるなら、それを解消することこそが日本にとって急務です。