The One with ...

思いついたことや作業メモなどを公開しています

Pythonによるジニ係数の計算コード

内容

ジニ係数Pythonを使って計算します.よく知られた定義ではなく,簡略化された定義式を使うと計算が速くなる,という例を紹介します.

ジニ係数の直感的意味

ジニ係数は,所得や資産などの非負の資源分配の不平等度を測定する指標です. たとえば,社会の全員に全く同じ量の富が分配されている場合にはジニ係数Gは0の値をとります.

y=[1,1,1,1,1,1]
G(y)=0

もし,たった1人が, その社会に存在する全ての富を独占している状態だと,ジニ係数は1の値をとります.

y=[1,0,0,0,0,0]
G(y)=0

つまり,ジニ係数は,完全に平等な状態であれば0,不平等が大きくなるほど1に近づく指標です.

ジニ係数の定義

よく知られたジニ係数の定義は 所得ベクトル 
y=(y_1, y_2, \ldots, y_n)
に対して,

\displaystyle{
G=\frac{1}{2 n ^2 \mu}\sum \sum  | y_i - y_j | \qquad  ただし \mu=\frac{y_1+y_2+\cdots+y_n}{n}
}

です(なお \sum \sum_{i=1}^{n}の略です.はてぶろ上では2重和がうまく表示できないので省略しました).この定義を使った場合,最大数は 1-1/nです. 最大値をぴったり1にしたい場合は

\displaystyle{
G=\frac{1}{2 n(n-1) \mu}\sum \sum  | y_i - y_j |
}

を使います.ジニ係数の定義の直感的な意味は,「全てのペアの所得の差の絶対値を合計して,基準化した量」です. これを計算するPythonコードを考えてみましょう.定義通りにコードに翻訳すると,例えば次のような関数になるでしょう.

import statistics
import math

def gini(y):
    m = statistics.mean(y)
    n = len(y)
    a = 2*m*(n*(n-1))
    ysum = 0
    for i in range(n):
            for j in range(n):
                ysum = ysum +(math.fabs(y[i]-y[j]))
    
    return(ysum/a)

ためしに計算してみましょう.

y=[1,0,0,0,0,0]
gini2(y)

Out[ ]: 1.0

y=[1,1,1,1,1,1]
gini2(y)

Out[ ]: 0.0

完全不平等の場合に1,完全平等な場合に0をたしかに出力しています.

y=[1,2,3,4,5,6]
gini2(y)

Out[ ]: 0.3333333333333333

一様分布の場合は,約G=1/3でした.

ところでジニ係数の定義を見て分かるとおり,所得ベクトル要素の差の絶対値を2重に足しています. |x_i-x_j|と|x_j-x_i|は常に同じ値なので 計算としてはずいぶん無駄な作業をしています. 上記の工夫のないコードでも,データが少なければ特に問題は無いのですが,データが増えてくると若干計算速度が気になります.

たとえばrange( )関数を使って,データを生成しながら計算してると,長さ2000くらいの短いリストでも出力までに時間がけっこうかかります.

import time #時間計測用
y=range(2000)    
start = time.time()
[gini(y),time.time() - start]

Out[ ]: [0.33366683341670833, 2.4708046913146973]

なんということだ!2.47秒もかかっている. これではシミュレーションでジニ係数を反復計算する際に面倒です.

ジニ係数の別の定義式

そこで,計算を早めるためにジニ係数の別の定義式を使います. 次の定義式は上述した式と同値であることが証明されています.ただしこの定義を用いる場合は,所得ベクトルを昇順に並び替える必要があります.

  \displaystyle G= \frac{ \sum i y_i}{ \sum y_i}-  \frac{n+1}{n}

ただし  y_1 \le y_2 \le \cdots \le y_n

en.wikipedia.org

この式を使えば総和が2重になっている部分が簡略化されるので,ぐっと計算量が減ります.最大値を1にしたい場合は調整用に n/(n-1)を掛けます.

  \displaystyle G=( \frac{ \sum i y_i}{ \sum y_i}-  \frac{n+1}{n} )  \frac{n}{n-1}

上記の定義にもとづくコードは例えば以下のようなものになるでしょう.

#sortして計算の効率化
def gini2(y):
    y.sort()
    n = len(y)
    nume = 0
    for i in range(n):
        nume = nume + (i+1)*y[i]
        
    deno = n*sum(y)
    return ((2*nume)/deno - (n+1)/(n))*(n/(n-1))

速くなったかどうか,確かめて見ましょう.

y=list(range(2000))
start = time.time()
[gini3(y),time.time() - start]

Out[9]: [0.3336668334167085, 0.0]

速くなりました!

以上です.