ローレンツ曲線とは
事象や資源の集中度合いを示す曲線。 所得や貯蓄の格差などを視覚的に示すのに有用。
書き方と解釈
データサンプルの値を階級分けし、
- 横軸:各階級の度数の累積相対度数
- 縦軸:各階級に属する値の合計の累積相対度数
をプロットする。
例えば以下のように、ある労働者グループの年収の階級別度数分布表において、横軸に「累積人数(相対)」、縦軸に「累積年収(相対)」を取る。
階級 | 階級値(万円) | 人数 | 階級内合計年収 | 累積人数(実数) | 累積人数(相対) | 累積年収(実数) | 累積年収(相対) |
---|---|---|---|---|---|---|---|
1 | 100 | 11 | 1100 | 11 | 0.0924 | 1100 | 0.0154 |
2 | 200 | 15 | 3000 | 26 | 0.2185 | 4100 | 0.0575 |
3 | 300 | 18 | 5400 | 44 | 0.3697 | 9500 | 0.1332 |
4 | 400 | 24 | 9600 | 68 | 0.5714 | 19100 | 0.2679 |
5 | 500 | 18 | 9000 | 86 | 0.7227 | 28100 | 0.3941 |
6 | 600 | 11 | 6600 | 97 | 0.8151 | 34700 | 0.4867 |
7 | 700 | 6 | 4200 | 103 | 0.8655 | 38900 | 0.5456 |
8 | 800 | 3 | 2400 | 106 | 0.8908 | 41300 | 0.5792 |
9 | 900 | 4 | 3600 | 110 | 0.9244 | 44900 | 0.6297 |
10 | 1000 | 1 | 1000 | 111 | 0.9328 | 45900 | 0.6438 |
11 | 1100 | 0 | 0 | 111 | 0.9328 | 45900 | 0.6438 |
12 | 1200 | 2 | 2400 | 113 | 0.9496 | 48300 | 0.6774 |
13 | 1300 | 0 | 0 | 113 | 0.9496 | 48300 | 0.6774 |
… | … | … | … | … | … | … | … |
35 | 3500 | 0 | 0 | 113 | 0.9496 | 48300 | 0.6774 |
36 | 3600 | 0 | 0 | 113 | 0.9496 | 48300 | 0.6774 |
37 | 3700 | 3 | 11100 | 116 | 0.9748 | 59400 | 0.8331 |
38 | 3800 | 0 | 0 | 116 | 0.9748 | 59400 | 0.8331 |
39 | 3900 | 1 | 3900 | 117 | 0.9832 | 63300 | 0.8878 |
40 | 4000 | 2 | 8000 | 119 | 1.0000 | 71300 | 1.0000 |
これを見れば、例えば「ローレンツ曲線が (0.8, 0.5) よりも下を通る」→「低所得な順に労働者の8割の年収を足しても、全労働者の年収合計の半分に満たない」といったことが分かる。
このように、(0, 0) と (1, 1) を結ぶ直線(完全平等線)と比べてローレンツ曲線の膨らみが大きいほど、偏りの大きい分布であると言うことができる。
(描画に使った Python コード)
from matplotlib import pyplot as plt
import numpy as np
def calc_cum_rel(vs, dv):
"""
累積相対度数を計算する
vs : 値のリスト(全て正の値を想定)
dv : 階級幅
"""
hist_size = int(max(vs)//dv+1)
cnt_v = [0] * hist_size
sum_v = [0] * hist_size
for v in vs:
i = int(v//dv)
cnt_v[i] += 1
sum_v[i] += v
# 累積度数の計算
cnt_v_cum = [0, cnt_v[0]]
sum_v_cum = [0, sum_v[0]]
for i in range(1, hist_size):
cnt_v_cum.append(cnt_v_cum[-1] + cnt_v[i])
sum_v_cum.append(sum_v_cum[-1] + sum_v[i])
# 累積相対度数の計算
cnt_v_cum_rel = np.array(cnt_v_cum) / cnt_v_cum[-1]
sum_v_cum_rel = np.array(sum_v_cum) / sum_v_cum[-1]
return (cnt_v_cum_rel, sum_v_cum_rel)
def calc_gini(cnt_v_cum_rel, sum_v_cum_rel):
"""
ジニ係数を計算
cnt_v_cum_rel : 度数の累積相対度数
sum_v_cum_rel : 値の累積相対度数
"""
gini = 0
for i in range(len(cnt_v_cum_rel)-1):
h = cnt_v_cum_rel[i+1] - cnt_v_cum_rel[i]
a_top = cnt_v_cum_rel[i] - sum_v_cum_rel[i]
a_bottom = cnt_v_cum_rel[i+1] - sum_v_cum_rel[i+1]
gini += h * (a_top + a_bottom) * 0.5
gini /= 0.5
return gini
def draw_lorenz_curve(vs, dv, n_label, v_label):
"""
ローレンツ曲線を描画
vs : 値のリスト(全て正の値を想定)
dv : 階級幅
n_label :
v_label :
"""
cnt_v_cum_rel, sum_v_cum_rel = calc_cum_rel(vs, dv)
hist_size = len(cnt_v_cum_rel) - 1
gini = calc_gini(cnt_v_cum_rel, sum_v_cum_rel)
# 描画
fig, ax = plt.subplots(1, 2, figsize=(10, 4))
plt.subplots_adjust(wspace=0.4)
ax[0].set_title('Histogram')
ax[0].hist(vs, bins=hist_size)
ax[0].set_xlabel(v_label)
ax[0].set_ylabel(n_label)
ax[1].set_title('Lorenz Curve')
ax[1].plot(cnt_v_cum_rel, sum_v_cum_rel, marker='.', label='Lorenz curve ($G = {:.4f}$)'.format(gini))
ax[1].plot([0,1], [0,1], label='complete equality line')
ax[1].set_xlabel('Cumulative relative frequency ({})'.format(n_label))
ax[1].set_ylabel('Cumulative relative frequency ({})'.format(v_label))
ax[1].legend()
ax[1].grid()
plt.show()
vs = [100]*11+[200]*15+[300]*18+[400]*24+[500]*18+[600]*11+[700]*6+[800]*3+[900]*4+[1000]*1+[1200]*2+[3700]*3+[3900]*1+[4000]*2
draw_lorenz_curve(vs, dv=100, n_label='worker', v_label='salary')
ジニ係数
= Gini index
定義
ローレンツ曲線の偏り・不平等の程度を数値化した指数。
- $S_1$:完全平等線と実際のデータのローレンツ曲線とで囲まれた面積
- $S_2$:完全平等線と完全に不平等なときのローレンツ曲線とで囲まれた面積
として、ジニ係数 $G$ は
\[G = \cfrac{S_1}{S_2}\]で計算される。
偏りが大きいほどローレンツ曲線は右に膨らむので、$S_1$ が大きくなり、ジニ係数 $G$ も大きくなる。
データからの計算方法
データサンプル数 $n$ が十分に大きい時、$S_2$ は底辺・高さがともに長さ1の直角二等辺三角形に近づいていくから、$S_2 = 0.5$ と近似できる。
$S_1$ は以下の図より、
- 台形 $A_0A_1B_1B_0$(三角形)の面積
- 台形 $A_1A_2B_2B_1$ の面積
- 台形 $A_2A_3B_3B_2$ の面積
- …
を足し上げることで計算できる。
実データの例
2010年のメジャーリーグ年俸
rdatasets より、2010年のメジャーリーグの年俸データを利用。
(描画に使った Python コード)
import statsmodels.api as sm
# 2010年のメジャーリーグ選手の年俸データを読み込み
ds = sm.datasets.get_rdataset("mlb", "openintro")
df = ds.data.dropna()
"""
player team position salary
0 Brandon Webb Arizona Diamondbacks Pitcher 8500.0
1 Danny Haren Arizona Diamondbacks Pitcher 8250.0
2 Chris Snyder Arizona Diamondbacks Catcher 5250.0
.. ... ... ... ...
825 Ross Detwiler Washington Nationals Pitcher 400.0
826 Jesse English Washington Nationals Pitcher 400.0
827 Willy Taveras Washington Nationals Outfielder 400.0
"""
draw_lorenz_curve(df['salary'], dv=1000, v_label='salary', n_label='player')