ジニ不純度の算出3 - Python, R, CoffeeScript
前々回 と前回 に続き、下記のようなプログラム言語でジニ不純度(ジニ係数)の算出処理を同様に実装してみました。
今回のソースは http://github.com/fits/try_samples/tree/master/blog/20140622/
Python で実装
- Python 2.7
- IronPython 2.7
Python では itertools モジュールの groupby
や combinations
関数が使えます。
groupby
は Haskell と同様に隣り合う同じ値をグルーピングできます。 (今回のケースでは sorted
でソートが必要)
groupby 結果の値部分(グルーピング部分)には直接 len
関数を使えないようなので list
関数でリスト化してから len を適用します。
また、combinations
関数を使用すると Scala の combinations と同様に要素の組み合わせを取得できます。(下記では AB、AC、BC の 3種類)
gini.py
from itertools import * def size(xs): return float(len(xs)) # (a) 1 - (AA + BB + CC) def giniA(xs): return 1 - sum(map(lambda (k, g): (size(list(g)) / size(xs)) ** 2, groupby(sorted(xs)))) def countby(xs): return map(lambda (k, v): (k, size(list(v))), groupby(sorted(xs))) # (b) AB * 2 + AC * 2 + BC * 2 def giniB(xs): return sum(map( lambda ((xk, xv), (yk, yv)): xv / size(xs) * yv / size(xs) * 2, combinations(countby(xs), 2) )) vlist = ["A", "B", "B", "C", "B", "A"] print giniA(vlist) print giniB(vlist)
実行結果
> python gini.py 0.611111111111 0.611111111111
Python 3 で実行するには
Python 3.4 で実行するにはラムダ式と print のところを書き換える必要があります。
Python 3 のラムダ式ではタプル内の複数の要素を個別に引数として取得できないようなので、Python 2 のように lambda (k, v): ・・・
とは書けず、lambda x: ・・・
として個々の要素をインデックスで参照 (x[0]
等) する事になります。
gini3.py (Python 3.4)
・・・ # (a) 1 - (AA + BB + CC) def giniA(xs): return 1 - sum(map(lambda x: (size(list(x[1])) / size(xs)) ** 2, groupby(sorted(xs)))) ・・・ # (b) AB * 2 + AC * 2 + BC * 2 def giniB(xs): return sum(map( lambda x: x[0][1] / size(xs) * x[1][1] / size(xs) * 2, combinations(countby(xs), 2) )) vlist = ["A", "B", "B", "C", "B", "A"] print(giniA(vlist)) print(giniB(vlist))
実行結果 (Python 3.4)
> python gini3.py 0.6111111111111112 0.611111111111111
R で実装
R では table
関数で要素毎のカウント値を取得でき、combn
関数で Scala や Python の combinations と同様の組み合わせを行列(matrix)として取得できます。
lapply
の結果(リスト)には sum
関数を適用できないようなので Reduce
を使って合計しています。
また、apply
の第2引数を 2 とすれば列単位にデータを処理できます。
gini.R
# (a) 1 - (AA + BB + CC) giniA <- function(xs) { 1 - Reduce("+", lapply(table(xs), function(x) (x / length(xs)) ^ 2)) } # (b) AB * 2 + AC * 2 + BC * 2 giniB <- function(xs) { sum(apply(combn(table(xs), 2), 2, function(x) (x[1] / length(xs)) * (x[2] / length(xs)) * 2)) } list <- c("A", "B", "B", "C", "B", "A") giniA(list) giniB(list)
実行結果
・・・ > giniA(list) [1] 0.6111111 > giniB(list) [1] 0.6111111
備考
各処理の結果は下記のようになります。
table(list) の結果
> table(list) list A B C 2 3 1
combn(table(list), 2) の結果
> combn(table(list), 2) [,1] [,2] [,3] [1,] 2 2 3 [2,] 3 1 1
ちなみに、上記は以下のような組み合わせのカウント値です。
[,1] [,2] [,3] [1,] "A" "A" "B" [2,] "B" "C" "C"
CoffeeScript で実装
- CoffeeScript 1.7
CoffeeScript では、Underscore.js 等のライブラリを使用しない限り、グルーピングや組み合わせの処理を自前で実装する事になると思います。
gini.coffee
countBy = (xs) -> xs.reduce (acc, x) -> acc[x] ?= 0 acc[x]++ acc , {} sum = (xs) -> xs.reduce (acc, x) -> acc + x # (a) 1 - (AA + BB + CC) giniA = (xs) -> 1 - sum( (v / xs.length) ** 2 for k, v of countBy(xs) ) flatten = (xs) -> xs.reduce (x, y) -> x.concat y combination = (xs) -> flatten( [x, y] for x in xs when x isnt y for y in xs ) # (b) BA + CA + AB + CB + AC + BC giniB = (xs) -> sum( (x[1] / xs.length) * (y[1] / xs.length) for [x, y] in combination([k, v] for k, v of countBy(xs)) ) list = ["A", "B", "B", "C", "B", "A"] console.log giniA(list) console.log giniB(list)
実行結果
> coffee gini.coffee 0.6111111111111112 0.611111111111111