(chap:9-solow)=
import japanize_matplotlib
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import py4macro
import statsmodels.formula.api as smf
# numpy v1の表示を使用
np.set_printoptions(legacy='1.21')
# 警告メッセージを非表示
import warnings
warnings.filterwarnings("ignore")
前章では差分方程式について説明し,簡単な経済モデルを使いコードの書き方を説明した。本章では,差分方程式の応用となるソロー・モデルを考える。ソロー・モデルは1987年にノーベル経済学賞を受賞したRobert M. Solowによって考案された経済成長モデルであり,マクロ経済学の代表的な理論モデルの一つである。今でも盛んに研究が続く経済成長のバックボーン的な存在である。モデルの説明の後,Python
を使い動学的な特徴を明らかにし,線形近似を使った安定性の確認もおこなう。また理論的な予測がデータと整合性があるかについてもPenn World Tableを使って検討する。本章の内容は次章で議論する所得収斂の分析の基礎となる。
ここではモデルの具体的な説明については教科書に譲るとして,簡単にモデルを紹介し,重要な式をまとめることにする。
<記号>
<一人当たりの変数>
全ての市場は完全競争である閉鎖経済を考えよう。この経済には一種類の財(ニューメレール財)しかなく,消費・貯蓄・投資に使われる。財は次の生産関数に従って生産される。
$$ Y_t=AK_t^aL_t^{1-a},\quad 0<a<1 $$(eq:8-production_level)
両辺を$L_t$で割ると一人当たりの変数で表した生産関数となる。
$$ y_t=Ak_t^a $$(eq:8-production)
消費者は所得の割合$s$を貯蓄するが,このモデルの中で消費者の役割はこれだけであり,残り全ては生産側で決定される。貯蓄は$sY_t$であり投資$I_t$と等しくなる。
$$ sY_t=I_t $$(eq:8-syi)
$t$期の投資により$t+1$期の資本ストックは増加するが,毎期ごと資本は$d$の率で減耗する。即ち,投資と資本ストックには次の関係が成立する。
$$ K_{t+1}-K_{t}=I_t-dK_t $$(eq:8-kk)
ここで左辺は資本ストックの変化であり,右辺は純投資である。$I_t$は粗投資,$dK_t$は減耗した資本である。式eq:8-syi,eq:8-kk,eq:8-productionを使うと資本の蓄積方程式が導出できる。
$$ K_{t+1} = sAK_t^aL^{1-a} + (1-d)K_t $$(eq:8-kkdot)
両辺を$L_t$で割ることで一人当たりの変数で表すことができる。右辺は単純に一人当たりの変数に直し,左辺は次のように書き換えることに注意しよう。
$$ \frac{K_{t+1}}{L_t}=\frac{K_{t+1}}{L_{t+1}}\frac{L_{t+1}}{L_t} =k_{t+1}(1+n) $$従って,$t+1$期の一人当たりの資本ストックを決定する式は次式で与えられる。
$$ k_{t+1} = \frac{sAk_t^a + (1-d)k_t}{1+n} $$(eq:8-kdot)
この式は非線形の差分方程式だが,前章でも述べたように,考え方は線形差分方程式と同じであり,数値計算のためのPython
コードに関しては大きな違いはない。また式eq:8-kdotを資本ストックの成長率を示す式に書き換えることもできる。
(eq:8-kgrowth)
この式から資本が蓄積され$k_t$が増加すると,その成長率は減少していくことが分かる。
産出量の動学は生産関数eq:8-productionを使うことによって$y_t$の動きを確認できる。例えば,産出量の成長率を考えてみよう。式eq:8-productionを使うと
$$ \frac{y_{t+1}}{y_t}-1 =\left(\frac{k_{t+1}}{k_t}\right)^{\alpha}-1 $$(eq:8-ygrowth)
となり,一人当たり資本ストックの成長率と同じような動きをすることが分かると思う。
次に定常状態を考えよう。定常状態では資本ストックは一定なり,資本の成長率である式eq:8-kgrowthの右辺はゼロになる。 定常値は次のように確認することができる。
$$ \begin{align*} \frac{k_*}{k_*}-1 &=\frac{sAk_*^{-(1-a)} - (n+d)}{1+n} \\ &\Downarrow \\ k_* &=\left( \frac{As}{n+d} \right)^{\frac{1}{1-a}} \end{align*} $$(eq:8-kss)
この値を生産関数eq:8-productionに代入することにより一人当たりGDPの定常値を求めることができる。
$$ y_*=Ak_*^{a} $$(eq:8-yss)
{numref}fig:8-solow
は資本ストックの動学的均衡を示している。
{figure}
---
scale: 30%
name: fig:8-solow
---
一人当たり資本ストックの動学
差分方程式eq:8-kdotを使って資本ストックの変化をプロットするが,以前と同じようにDataFrame
を生成する関数を定義しよう。
def solow_model(k0, A=10, a=0.3, s=0.3, n=0.02, d=0.05, T=100):
"""引数
k0: 資本の初期値
A: 生産性
a: 資本の所得比率 (a<1)
s: 貯蓄率 (s<1)
n: 労働人口成長率(%)
d: 資本減耗率 (d<1)
T: ループによる計算回数
戻り値
資本と産出量からなるDataFrame"""
k = k0
y = A * k0**a
k_lst = [k]
y_lst = [y]
for t in range(T):
k = ( s * A * k**a + (1-d) * k )/( 1+n )
y = A * k**a
k_lst.append(k)
y_lst.append(y)
# DataFrameの作成
dic = {'capital':k_lst, 'output':y_lst}
df = pd.DataFrame(dic)
return df
引数に使うパラーメータには次の値を使ってプロットしてみよう。
df = solow_model(k0=200)
df.plot(subplots=True)
pass
異なる初期値を使って資本の変化をプロットしてみる。
initial_lst = range(100,300,10) # 1
ax = pd.DataFrame({'capital':[],
'output':[]}).plot(legend=False) # 2
for i in initial_lst: # 3
solow_model(k0=i).plot(y='capital', legend=False, ax=ax)
ax.set(title='ソロー・モデルの移行過程', # 4
xlabel='ループの回数',
ylabel='一人当たり資本ストック')
pass
{admonition}
:class: dropdown
* `#1`:`range(100,300,10)`は100から300-1=299までの間の数を10間隔で生成する。100,110,120,$cdots$,290となる。
* `#2`:空の`DataFrame`を使って空の軸を作成し,`ax`に割り当てる。
* `#3`:`initial_list`に対して`for`ループを設定する。一回のループごとに以下をおこなう。
* `i`は`k0`の引数に使う値になる。
* `.plot()`を使い`ax`に図をプロットする。これにより図が重ねて描かれる。
* `#4`:`ax`のメソッド`set()`を使い,引数を使い以下を追加する。
* `title`:図のタイトル
* `xlabel`:横軸のラベル
* `ylabel`:縦軸のラベル
* この3行を次のように書いても同じ結果となる。
```
ax.set_title('ソロー・モデルの移行過程')
ax.set_xlabel('ループの回数')
ax.set_ylabel('一人当たり資本ストック')
```
この3つを`.set()`でまとめて書いたのが上のコードである。3つに分けて書く利点はフォントの大きさを指定できることだろう。
図から初期値に関わらず定常値に収束していることが分かる。即ち,定常状態である長期均衡は安定的である。
次に定常状態での変数の値を計算してみよう。
def calculate_steady_state(A=10, a=0.3, s=0.3, n=0.02, d=0.05):
k_ss = ( s * A / (n+d) )**( 1/(1-a) )
y_ss = A * k_ss**( a/(1-a) )
return k_ss, y_ss
ss = calculate_steady_state()
print(f'定常状態での資本ストック:{ss[0]:.1f}'
f'\n定常状態での産出量: {ss[1]:.1f}')
{numref}fig:8-solow
は,定常状態は安定的であることを示している。またシミュレーションの結果からも定常状態の安定性が確認できる。次に,線形近似を使って解析的に安定性を確認してみることにする。また線形近似は真の値からの乖離が発生するが,その乖離がどの程度のものかをコードを使って計算することにする。
<テイラー展開による1次線形近似>
関数$z=f(x)$を$x_*$でテイラー展開すると次式となる。
$$ z=f(x^*)+\left.\frac{df}{dx}\right|_{x=x_*}(x-x_*) $$
ソロー・モデルの式に当てはめると次のような対応関係にある。
公式に従って計算してみよう。
$$ \begin{align*} k_{t+1} &=\frac{Ask_{*}^{\alpha}+(1-d)k_*}{1+n} +\frac{1}{1+n}\left[\frac{Asa}{k_t^{1-\alpha}}+(1-d)\right]_{k_t=k_*}(k_t-k_*) \\ &=k_{*} +\frac{1}{1+n}\left[\frac{Asa}{k_*^{1-\alpha}}+(1-d)\right](k_t-k_*) \\ &=k_*+\frac{1}{1+n}\left[\frac{Asa}{As/(n+d)}+(1-d)\right](k_t-k_*) \\ &=k_*+\frac{a(n+d)+1-d-n+n}{1+n}(k_t-k_*) \\ &=k_*+\frac{1+n-(1-a)(n+d)}{1+n}(k_t-k_*) \\ &=(1-\lambda) k_t + \lambda k_* \end{align*} $$(eq:8-kapprox)
ここで
$$ \lambda\equiv \frac{(1-a)(n+d)}{1+n} $$(eq:8-lambda)
3行目は定常状態の式eq:8-kdotと式eq:8-kssを使っている。式eq:8-kapproxは$k_{t+1}$と$k_t$の線形差分方程式になっており,$k_t$の係数は
$$ 0<1-\lambda<1 \quad\because 0<a,d<1 $$(eq:8-coef_of_kt)
が成立する。{numref}fig:8-solow
の赤い直線が式eq:8-kapproxである。従って,初期値$k_0>0$からスタートする経済は必ず長期的均衡に収束することがわかる。
式eq:8-lambdaは$k_t$の係数だが,その裏にあるメカニズムを考えてみよう。特に,$a$の役割を考える。$a$は資本の所得比率であり,目安の値は1/3である。そしてソロー・モデルにおける重要な役割が資本の限界生産性の逓減を決定することである。この効果により資本ストックが増加する毎に産出量も増加するがその増加自体が減少する。この効果により,{numref}fig:8-solow
の曲線は凹関数になっており,生産関数eq:8-productionの場合は必ず45度線と交差することになる。即ち,資本の限界生産性の逓減こそが$k_t$が一定になる定常状態に経済が収束す理由なのである。この点がソロー・モデルの一番重要なメカニズムとなる。
ここで$a$が上昇したとしよう。そうなると資本の限界生産性の逓減の効果は弱くなり,資本ストックが増加しても産出量の増加自体の減少は小さくなる。また式eq:8-kssが示すように定常状態での資本ストックはより大きくなる。これは{numref}fig:8-solow
で曲線が上方シフトしている考えると良いだろう。
{admonition}
:class: note
更に$a$を上昇させて$a=1$になるとどうなるのだろう。この場合,資本の限界生産性は逓減せず一定となる。そして$k_t$の係数である式[](eq:8-coef_of_kt)は1になってしまい,$k_t$が一定になる定常状態が存在しなくなる。$a=1$となる極限の状態を内生的成長と呼ぶ。ここでは立ち入った議論はしないが,内生的成長の典型的な生産関数は次式となり,
$$y_t=Ak_t$$
この生産関数に基づくモデルは$AK$モデルと呼ばれる。資本の限界生産性は$A$で一定になることが分かると思う。
上の議論から$\lambda$が定常状態の安定性を決定することが分かったが,$\lambda$の値はどのように解釈できるだろうか。例えば,$\lambda$が大きい場合と小さい場合では何が違うのだろうか。次式は式eq:8-kapproxの最後の等号を少し書き換えたものである。
$$ k_*-k_{t+1}=(1-\lambda)(k_*-k_t) $$(eq:8-approx_lastline)
左辺は$t+1$期において定常状態までの残りの「距離」であり,右辺の$k_*-k_t$は$t$においての定常状態までの残りの「距離」である。後者を次の様に定義し
$$z_t\equiv k_*-k_t$$式eq:8-approx_lastlineを整理すると次式となる。
$$ \frac{z_{t+1}-z_t}{z_t}=-\lambda $$(eq:8-kspeed)
左辺は$t$期と$t+1$期において定常状態までの「距離」が何%減少したかを示す資本ストックの収束速度である。このモデルの中での収束速度の決定要因は資本の所得比率$\alpha$,労働人口増加率$n$と資本の減耗率$d$ということである。
ここでは$a$の役割に着目し,なぜ$a$の上昇は収束速度の減少をもたらすのかを直感的に考えてみよう。この点を理解するために,まず$a$は資本の限界生産性の逓減を決定するパラーメータであることを思い出そう。$a$が上昇するとその効果は弱まる。即ち,資本ストックが1単位増加すると産出量は増え,その増加分が減少するのが「逓減」であるが,その減少が小さくなるのである。これにより(上で説明したように)$k_t$が一定となる定常状態は増加することになる。重要な点は,定常状態の増加の意味である。定常状態はマラソンのゴールの様なものである。トップランナーはゴールすると走るのを止め,後続ランナーはトップランナーとの「距離」を縮めることができる。定常状態の増加は,ゴールが遠くなることと同じである。ゴールが遠のくとトップランナーは走り続けるわけだから,それだけ距離を縮めることが難しくなり収束速度が減少することになる。極端なケースとして$a=1$の場合,$k_t$が一定になる定常状態は存在せず,ゴールがない状態が永遠に続いており,永遠に収束しないということである。言い換えると,資本の限界生産性の逓減($a<1$)こそが「距離」を縮めキャッチアップを可能にするメカニズムなのだ。
労働人口増加率$n$と資本の減耗率$d$の上昇は収束速度を速くする。式eq:8-kssから分かる様に,$n$もしくは$d$の上昇は定常状態を減少させる。即ち,ゴールはより近くになるということだ。
これである程度キャッチアップのメカニズムが分かったと思うが,今までの議論で足りないものが2点あるので,それらについて簡単に言及する。第一に,ここで考えたソロー・モデルには技術進歩が抜けている(一定な$A$を仮定した)。この点を導入してこそソロー・モデルのフルバージョンであり,その場合の労働効率1単位当たり資本ストック($K_t/(A_tL_t)$)の収束速度は次の式で与えられる。
$$ \lambda\equiv \frac{(1-a)(g+n+d+ng)}{(1+n)(1+g)} $$(eq:8-lambda_g)
ここで$g$は技術進歩率である。ソロー・モデルでは4つの変数が収束速度の決定要因になるる。$g=0$の場合,式eq:8-lambdaと同じになることが確認できる。第二に,式eq:8-lambdaは資本ストックの収束速度であり一人当たりGDPの収束速度と異なるのではないかという疑問である。実は同じである。これはコブ・ダグラス生産関数eq:8-productionを仮定しているからであり,対数の近似を使えば簡単に示すことができる。式eq:8-approx_lastlineを次のように書き直そう。
$$ \frac{k_{t+1}}{k_*}-1=(1-\lambda)\left(\frac{k_t}{k_*}-1\right) $$(eq:8-ks_rewrite)
ここで$\log(1+x-1)\approx x-1$の近似を使い左辺を次のように書き換える。
$$ \frac{k_{t+1}}{k_*}-1 \approx\log\left(\frac{k_{t+1}}{k_*}\right) =\log\left(\frac{y_{t+1}}{y_*}\right)^{\frac{1}{\alpha}} =\frac{1}{\alpha}\left(\frac{y_{t+1}}{y_*}-1\right) $$同様に$\dfrac{k_{t}}{k_*}-1$もこの形に書き換えることができる。後はこの関係を使うことにより,式eq:8-ks_rewriteを整理すると次式となる。
$$ y_*-y_{t+1}=(1-\lambda)(y_*-y_t) $$(eq:8-y_difference_eq)
式eq:8-approx_lastlineと同じ形になっているので所得の収束速度も式eq:8-kspeedと同じである。
次に関数solow_model()
を修正して線形近似の誤差を確かめてみよう。
def solow_model_approx(k0, A=10, a=0.3, s=0.3, n=0.02, d=0.05, T=20):
"""引数
k0: 資本の初期値
A: 生産性
a: 資本の所得比率 (a<1)
s: 貯蓄率 (s<1)
n: 労働人口成長率(n>=0)
d: 資本減耗率 (d<1)
T: ループによる計算回数
戻り値
線形近似モデルを使い計算した資本と産出量からなるDataFrame"""
k = k0
y = A * k0**a
k_lst = [k]
y_lst = [y]
# 定常状態
k_ss = ( s*A / (n+d) )**( 1 / (1-a) )
for t in range(T):
lamb = 1 - (1-a) * (n+d) / (1+n) # lambda
k = lamb*k + (1-lamb) * k_ss # 線形近似
y = A * k**a
k_lst.append(k)
y_lst.append(y)
# DataFrameの作成
dic = {'capital':k_lst, 'output':y_lst}
df = pd.DataFrame(dic)
return df
近似誤差を計算するために,上と同じ数値でシミュレーションをおこなう。
df_approx = solow_model_approx(k0=200, T=150)
非線形のモデルeq:8-kdotと線形近似のモデルeq:8-kapproxで計算された資本ストックを重ねて図示してみよう。
ax = df_approx.plot(y='capital', label='線形近似モデル')
df.plot(y='capital', label='ソロー・モデル', ax=ax)
pass
df_approx
が図示され、その上にdf
が重ねて表示されるが、殆ど同じのように見える。誤差を%で計算し図示してみよう。
( 100*( 1-df_approx['capital']/df['capital'] ) ).plot(marker='.')
pass
初期の資本ストック$k_0$は同じなので誤差はゼロであり,$t=1$から線形近似の誤差が現れることになる。誤差は単調ではない。{numref}fig:8-solow
の図が示しているように,階段のような形で増加していくためであり,その階段お大きさや進み具合が異なるためである。線形近似の値は大き過ぎるため負の値になっているが、誤差は大きくても約0.02%であり、定常状態に近づくにつれて誤差はゼロに近づいている。もちろん,誤差の値は初期値が定常値から離れればそれだけ大きくなっていく。
この節では長期均衡(定常状態)に焦点を当て,理論的な予測のデータとの整合性をチェックする。まず定常状態の特徴をまとめよう。式eq:8-kssとeq:8-yssを使いうと定常状態での一人当たり資本ストックとGDPは次式で与えれる。
$$ k_*= \left( \frac{sA}{n+d} \right)^{\frac{1}{1-a}}, \quad y_*=Ak_*^a $$この2つをそれぞれ試すこともできるが,同時に捉えるために2つの式の比率を考える。
$$ \frac{k_*}{y_*}=\frac{s}{n+d} =\left.\frac{K_t/L_t}{Y_t/L_t}\right|_{\text{定常状態}} =\left.\frac{K_t}{Y_t}\right|_{\text{定常状態}} $$(eq:8-kyratio)
この値は資本ストック対GDP比と等しいく,次のことが分かる。
この3つの予測が成立するか確かめるためにpy4macro
モジュールに含まれるPenn World Dataの次の変数を使う。
cgdpo
:GDP(2019年;生産側)cn
:物的資本ストック(2019年)csh_i
:対GDP比資本形成の比率emp
:雇用者数delta
:資本ストックの年平均減耗率(sec:8-data)=
1960年以降のデータをpwt
に割り当てる。
pwt = py4macro.data('pwt').query('year >= 1960')
1960年から2019年までの国別の貯蓄率の平均を計算するが必要がある。ここで説明しているDataFrame
のメソッド.groupby()
を使うのが最も簡単な計算方法だろう。ここでは異なる方法としてDataFrame
のメソッド.pivot()
を紹介する。.pivot()
はデータを整形する上で非常に便利なメソッドなので知って損はないだろう。
.pivot()
は,元のDataFrame
から列を選び,その列から新たなDataFrame
を作成する便利なメソッドである。実際にコードを実行して説明しよう。
saving = pwt.pivot(index='year', columns='country', values='csh_i')
saving.head()
{admonition}
:class: dropdown
`pwt`の3つの列`year`,`country`,`csh_i`を使って新たな`DataFrame`を作成し`saving`に割り当てている。引数は次の役割をする。
* `index`:新たな`DataFrame`の**行ラベル**を指定する。
* コードでは`year`が指定され行ラベルになっている。
* `columns`:新たな`DataFrame`の**列ラベル**を指定する。
* コードでは`country`が指定され列ラベルになっている。
* `values`:新たな`DataFrame`の**値**を指定する。
* コードでは`csh_i`が指定され,その値で`DataFrame`が埋め尽くされている。
1960年以降欠損値がない国だけを使うことにしよう。NaN
がある列を削除する必要があるので.dropna()
を使う。
saving = saving.dropna(axis='columns')
saving.head()
{admonition}
:class: dropdown
`.dropna()`は`NaN`がある行か列を削除する。行と列のどちらを削除するかは引数`axis`で指定するが,デフォルトは`'rows'`である。即ち,引数なしで`.dropna()`を実行すると`NaN`がある行が削除される。ここでは列を削除したいので,引数に`'columns'`を指定している。
AlbaniaやAngolaなどが削除されていることが確認できる。何ヵ国残っているか確認してみよう。
saving.shape
{admonition}
:class: dropdown
属性`.shape`は`DataFrame`の行の数(左の数字)と列(右の数字)の数を返す。
111ヵ国含まれていることが確認できた。次に,それぞれの列の平均を計算する。
saving = saving.mean().to_frame('saving_rate') # 1
saving.head()
{admonition}
:class: dropdown
1. `.mean()`はそれぞれの列の平均を計算し,`Series`を返す。後で`DataFrame`を結合するメソッド`.merge()`を使うために`.to_frame()`を使って`Series`を`DataFrame`に変換しており,引数`saving_rate`は列ラベルを指定している。もちろん引数を使わずに2行に分けることも可能である。
```
saving = saving.mean().to_frame()
saving.columns = ['saving_rate']
```
{tip}
上の計算では1960年以降に欠損値が一つでもあればその国は排除されたが,全ての年でデータが揃っている経済だけを扱いたい場合に便利に使える方法である。一方で,`.groupby()`を使うと欠損値があっても平均は計算されるので,単純に`.groupby()`を使うと1960年以降に欠損値がある経済も含まれることになる。それを避けるためには一捻り必要だが,それについては[](sec:9-saving)が参考になるだろう。
saving
と同じ方法で資本減耗率の平均からなるDataFrame
を作成する。
depreciation = pwt.pivot(index='year', columns='country', values='delta')
depreciation = depreciation.dropna(axis='columns')
depreciation.shape
110ヵ国含まれている。
depreciation = depreciation.mean().to_frame('depreciation')
平均成長率を計算するには1960年と2019年の労働人口だけで計算できるが,上と同じ方法で計算してみる。
emp = pwt.pivot(index='year', columns='country', values='emp')
emp = emp.dropna(axis='columns')
emp.shape
91ヵ国しか含まれていない。
emp_growth = ( ( emp.loc[2019,:]/emp.loc[1960,:] )**(1/(len(emp)-1))-1
).to_frame('employment_growth')
emp_growth.head()
2019年のcgdpo
とcn
を使って資本ストック対GDP比を含むDataFrame
を作成する。
ky_ratio = pwt.query('year == 2019') \
.loc[:,['country','cgdpo','cn']] \
.set_index('country') \
.dropna()
ky_ratio.head()
{admonition}
:class: dropdown
`.set_index(''country')`を使って`country`を行ラベルに指定し,`.dropna()`によって欠損値がある行は削除する。
資本ストック対GDP比の列の作成しよう。
ky_ratio['ky_ratio'] = np.log( ky_ratio['cn'] / ky_ratio['cgdpo'] )
含まれる国数を確認する。
ky_ratio.shape
180ヵ国含まれており,saving
やdepreciation
の国数よりも多くの国が含まれている。
上で作成したDataFrame
を結合する必要があり,そのためのDataFrame
のメソッド.merge()
の使い方を説明する。df_left
とdf_right
の2つのDataFrame
があるとしよう。df_left
を左のDataFrame
,df_right
を右のDataFrame
と呼ぶことにする。df_left
のメソッド.merge()
を使いdf_right
と結合する場合,次のコードとなる。
df_left.merge(df_right)
しかし注意が必要な点が2つある。
df_left
とdf_right
では行の並びが異なる可能性がある。これらの問題に対応するためのる引数が用意されている。
まず1つ目の問題は,ある列を基準列もしくは行ラベルに合わせて行を並び替えることにより対応できる。例えば,上で作成したDataFrame
であれば,行ラベルがcountry
になっているので,それに合わせて結合すれば良い。その場合の引数を含めたコードは次の様になる。
df_left.merge(df_right, left_index=True, right_index=True)
ここでのleft_index=True
とright_index=True
は行ラベルを基準に結合することを指定しており,デフォルトは両方ともFalse
である。行ラベルではなく,ある列を基準に結合した場合もあるだろう。その場合は次の引数を使う。
df_left.merge(df_right,
left_on=<`df_left`の基準列のラベル(文字列)>,
right_on=<`df_right`の基準列のラベル(文字列)>)
left_on
は基準列に使う左のDataFrame
にある列ラベルを文字列で指定する。同様にright_on
は基準列に使う右のDataFrame
にある列ラベルを文字列で指定する。デフォルトは両方ともNone
となっている。
2つ目の問題はhow
という引数を使うことにより対処できる。使える値は次の4つであり,いずれも文字列で指定する。
'inner'
:df_left
とdf_right
の両方の基準列にある共通の行だけを残す(デフォルト)。'left'
:df_left
の行は全て残し,df_right
からはマッチする行だけが残り,対応する行がない場合はNaN
が入る。'right'
:df_right
の行は全て残し,df_left
からはマッチする行だけが残り,対応する行がない場合はNaN
が入る。'outer'
:df_left
とdf_right
の両方の行を全て残し,マッチする行がない場合はNaN
を入れる。では実際に上で作成したDataFrame
を結合しよう。
上で作成したDataFrame
を結合する必要があり,そのためのPandas
の関数.merge()
の使い方を説明する。df_left
とdf_right
の2つのDataFrame
があるとしよう。df_left
を左のDataFrame
,df_right
を右のDataFrame
と呼ぶことにする。2つを結合する場合,次のコードとなる。
pd.merge(df_left, df_right)
しかし注意が必要な点が2つある。
df_left
とdf_right
では行の並びが異なる可能性がある。これらの問題に対応するためのに引数が用意されている。
まず1つ目の問題は,行ラベルを基準に,もしくはある列に合わせて行を並び替えることにより対応できる。例えば,上で作成したDataFrame
であれば,行ラベルがcountry
になっているので,それに合わせて結合すれば良い。その場合の引数を含めたコードは次の様になる。
pd.merge(df_left, df_right, left_index=True, right_index=True)
ここでのleft_index=True
とright_index=True
は行ラベルを基準に結合することを指定しており,デフォルトは両方ともFalse
である。行ラベルではなく,ある列を基準に結合したい場合もあるだろう。その場合は次の引数を使う。
pd.merge(df_left, df_right,
left_on=<`df_left`の基準列のラベル(文字列)>,
right_on=<`df_right`の基準列のラベル(文字列)>)
left_on
は基準列に使うdf_left
にある列ラベルを文字列で指定する。同様にright_on
は基準列に使うdf_right
にある列ラベルを文字列で指定する。デフォルトは両方ともNone
となっている。
2つ目の問題はhow
という引数を使うことにより対処できる。使える値は次の4つであり,いずれも文字列で指定する。
'inner'
:df_left
とdf_right
の両方の基準列にある共通の行だけを残す(デフォルト)。'left'
:df_left
の行は全て残し,df_right
からはマッチする行だけが残り,対応する行がない場合はNaN
が入る。'right'
:df_right
の行は全て残し,df_left
からはマッチする行だけが残り,対応する行がない場合はNaN
が入る。'outer'
:df_left
とdf_right
の両方の行を全て残し,マッチする行がない場合はNaN
を入れる。では実際に上で作成したDataFrame
を結合しよう。
for df_right in [saving, depreciation, emp_growth]: # 1
ky_ratio = pd.merge(ky_ratio, df_right, # 2
left_index=True, # 3
right_index=True, # 4
how='outer') # 5
{admonition}
:class: dropdown
1. `df_right`が上の説明の`df_right`に対応している。`[saving, depreciation, emp_growth]`は上で作成した`DataFrame`のリスト。
2. `ky_ratio`が上の説明の`df_left`に対応している。右辺で結合した`DataFrame`を左辺にある`ky_ratio`に割り当てている(際割り当て)。
3. `ky_ratio`の行ラベルを基準とすることを指定する。
4. `df_right`の行ラベルを基準とすることを指定する。
5. ここでの`'outer'`は左右の`DataFrame`のそれぞれの行ラベルを残し,値がない箇所には`NaN`を入れることを指定する。
結合の結果を表示してみよう。
ky_ratio.head()
列AlbaniaやAngolaはsaving
,depreciation
,emp_growth
のDataFram
には無いためNaN
が入っている。
結合後のky_ratio
の情報を表示してみよう。
ky_ratio.info()
180ヵ国が含まれるがNaN
がある国も多いことが分かる。
ここでは次の3つをおこなう。
for
ループを使ってこれらを同時に計算・表示する。まずky_ratio
の列ラベルをみると,回帰分析の説明変数に使う変数が最後の3つに並んでいる。
ky_ratio.columns[-3:]
これを使いfor
ループを組んでみよう。
for var in ky_ratio.columns[-3:]: #1
df_tmp = ky_ratio.copy() #2
res = smf.ols(f'ky_ratio ~ {var}', #3
data=df_tmp).fit() #4
bhat = res.params.iloc[1] #5
pval = res.pvalues.iloc[1] #6
df_tmp['Trend'] = res.fittedvalues #7
ax = df_tmp.plot.scatter(x=var, y='ky_ratio') #8
df_tmp.sort_values('Trend').plot(x=var, #9
y='Trend',
color='red',
ax=ax)
ax.set_title(f'トレンドの傾き:{bhat:.2f}\n' #10
f'p値:{pval:.3f}', size=20) #11
ax.set_ylabel('資本ストック対GDP比(対数)', #12
size=15) #13
ax.set_xlabel(f'{var}', size=20) #14
{admonition}
:class: dropdown
* `#1`:`ky_ratio`の最後の3列のラベルに対して`for`ループを組んで,`var`はその列ラベルを指す。
* `#2`:`ky_ratio`のコピーを作り`df_tmp`に割り当てる。`.copy()`は実態としては別物のコピーを作成する。詳細は割愛するが`.copy()`がないと実態は同じで参照記号のみが異なることになり,予期しない結果につながることを防ぐために`.copy()`を使っている。
* `#3`:回帰分析をおこなっているが,`f-string`を使い回帰式の説明変数を指定している。また回帰分析の結果を`res`に割り当てている。
* `#4`:`data`で回帰分析のデータの指定をおこない,`.fit()`で自動計算!
* `#5`:`res`の属性`.params`は推定値を2つ返すが,1番目に傾きの推定値が格納されているため`.iloc[1]`で抽出し,`bhat`に割り当てている。
* `#6`:`res`の属性`.pvalues`は$p$値を2つ返すが,1番目に傾きの$p$値が格納されているため`.iloc[1]`で抽出し,`pval`に割り当てている。
* `#7`:`res`の属性`fittedvalues`は予測値を返すが,それを新たな列(ラベルは`Trend`)として`df_tmp`に追加している。
* `#8`:`df_tmp`を使い横軸は`var`,縦軸は`ky_ratio`の散布図を表示し、その軸を`ax`に割り当てる。
* `#9`:`df_tmp`を使い横軸は`var`,縦軸は`ky_ratio`の回帰直線を表示。その際、メソッド`.sort_values('Trend')`で列`Trend`に従って昇順に並び替える。これはトレンド線が正確に表示されるために必要。
* `#10`:図のタイトルを設定する。
* `f-string`を使い`bhat`の値を代入する。
* `:.2f`は小数点第2位までの表示を指定。
* `\n`は改行の意味。
* 行を変えるので`\n`の後に`'`が必要となる。
* `#11`:`f-string`を使い`pval`の値を代入する。`:.3f`は小数点第3位までの表示を指定し,`size=20`はフォントの大きさの指定。
* `#12`:縦軸のラベルの設定。
* `#13`:フォントの大きさの指定。
* `#14`:横軸のラベルの設定であり,`f-string`を使い`var`の値を代入している。`size=20`はフォントの大きさの指定。
3つの図からソロー・モデルの理論的予測はデータと整合性があることが確認できる。ここで注意する点が一つある。式eq:8-kyratioは因果関係を予測している。例えば,貯蓄率が高くなることにより長期的な一人 当たりGDPは増加する。一方,トレンド線は因果関係を示しているのではなく単なる相関関係を表している。ソロー・モデルの因果関係を計量経済学的に検討するにはさまざまな要因の検討が必要になり,本章の域を超える事になる。