統計
Swift

try!Swiftの感想とWilsonScoreIntervalの話

More than 3 years have passed since last update.


try!Swiftカンファレンスに参加しました!

3/2 - 3/4の3日間渋谷マークシティ内のサイバーエージェント社で開催されたswiftの国際カンファレンスです。

感想は他の人が書いてるのと大体同じで、最高だった、の一言。

セッションは難しい内容も多かったですが、3日間を通じてswiftライクな書き方とか、swiftコミュニティへの貢献の仕方とか、思想的な話とか本当に学ぶ事が多かったです。まだ復習しきれてないセッションも結構あるのでまだもうちょっと自分のtry!Swiftは続きそうです。

あと、英語が話せるようになりたい!って本気で思いました・・。来年のtry!Swiftでは積極的に外国の方と交流できるように頑張りたいです。

不満点は特に無いです。自分はスタッフとしての参加でしたので、強いて言うなら前日のノベルティをトートバッグにつめる作業が地獄でした。

とにかく、色々なセッションを聞けて、色々なエンジニアの方と交流が出来て、最高のイベントでした!来年も楽しみにしています!


WilsonScoreIntervalの話

try!Swiftの3日目、Diana Zmudaさんの「Swiftトレーニング 統計学を例に」という発表がありました。

セッションの内容はにわたこさんが自身のブログ(http://niwatako.hatenablog.jp/entry/2016/03/04/145839) でまとめて下さっています!

このセッションの本質は恐らくswiftでエレガントに数学、統計学、自然言語処理が出来るといった話で、具体的な実装の中身は多くの人にとってそんなに重要ではないのかもしれません。

ですが、今回はその中の「Rating」のトピックに関してちょっとだけ詳しく書きたいと思います。私は統計学素人なので間違ってる点があったらすいません。。


目的

アニメ作品に対して「Good」と「Bad」のどちらかに投票できるとします。

その投票の結果から、アニメ作品のランキングを生成することが目標です。


方法

それぞれ投票数がバラバラなものに対してどのようにランキング付けするのが妥当でしょうか?

例えばすぐ思いつく方法として「Good評価の数-Bad評価の数」がありますが、これはすぐにダメだとわかります。何故なら投票の数は作品によって異なるからです。

では、「Good評価の数/トータル投票数」はどうでしょうか。

これもうまく機能しません。何故ならGood評価100,Bad評価1のアニメと、Good評価1,Bad評価0のアニメを比べた時、後者の方が順位が高くなってしまうからです。これは恐らく望ましい結果ではありません。

そこで、WilsonScoreIntervalと呼ばれる方法を用います。これは、信頼区間を導くために用いられる方法です。教科書によくのっている通常の公式とは違い、試行回数が少ない、又は極端な可能性を持つものに対して良好な結果が出るという特性があります。

公式は以下のようなものになります。

p^は標本確率

zはa%信頼区間における逆正規累積分布関数の値(95%信頼区間なら、標準正規分布の2.5%、97.5%の位置である±1.96)

nは標本数。

3e90a1ab0c229383f3affa0dcd952aa8.png

(wikipediaから引用:https://en.wikipedia.org/wiki/Binomial_proportion_confidence_interval)

これによって母集団のGoodの投票確率orBadの投票確率を現在の値(標本)から推定することが出来ます。

今回の例ではGoodの投票率の下限値(最低でもGoodの割合はこれ以上ではあるだろう、という値)でランク付けします。

上限値でランク付けするのはよくないです。例えばGoodが1,Badが0の時の区間推定の上限値は1.0になってしまいますから。

では、Diana Zmudaさんが資料に提示してくださっていたソースコードを見てみます。(特殊文字に一部資料とは表記が違う点があります)

protocol Reteable{

var votes: (up:Int,down:Int){ get }
}

extension CollectionType where Generator.Element:Reteable{
func sortByWillsonRanking() -> [Generator.Element]{
return self.sort{
return wilsonConfidenveInterval(
$0.votes.up,negative: $0.votes.down).lower > wilsonConfidenveInterval($1.votes.up,negative:$1.votes.down).lower

}
}

func wilsonConfidenveInterval(positive:Int,negative:Int,confidence:Double=0.95)->(lower:Double,upper:Double){
guard case let n = Double(positive+negative) where n != 0 else{
return (0.0,0.0)
}
// let z = inverseNormalCDF(1.0 - (1.0 - confidence)/2.0)
let z = 1.96
let z2 = (z * z)
let p = 1.0 * Double(positive)/n
let lhs = p + z2 / (2*n)
let rhs = z * sqrt((p * (1-p) + z2 / (4 * n))/n)
let divisor = 1 + z2 / n
let lowerBound = (lhs - rhs) / divisor
let upperBound = (lhs + rhs) / divisor
return (lowerBound,upperBound)
}
}

struct Anime:Reteable{
let name:String
let votes:(up:Int,down:Int)

init(name:String,votes:(up:Int,down:Int)){
self.name = name
self.votes = votes
}
}

let animeTT = Anime(name: "Tenjo Tenge", votes: (77,14))
let animeMM = Anime(name: "Madoka Magica", votes: (90,78))
[animeTT,animeMM].sortByWillsonRanking()

wilsonConfidenveInterval関数を見ます。

まず、zの値ですがinverseNormalCDF(1.0 - (1.0 - confidence)/2.0)となっており逆正規累積分布関数が必要なのですが、実装してないので今回はconfidenceは常に0.95だとしておいて、95%信頼区間の値である1.96を入れておきます。

(資料ではinverseNormalCDF(1.0 - confidence/2.0)となっていましたが、ミスでしょうか?)

pは標本から読み取れるGood評価の推定値です。

lhs、rhsは先に述べた公式の左辺と右辺のことです。

これで95%信頼区間における下限値と上限値が求めることが出来ます!

あとはこの下限値を使ってソートしたらランキング完成です!

因みに

「"Tenjo Tenge" Good:77 Bad:14」の区間は0.758ー0.906。

「"Madoka Magica" Good:90 Bad:78」の区間は0.460ー0.609でした。

母集団のGoodの率が最低でも75.8%はあるだろうと推定できるTenjo Tengeの方がランクは上だということがわかりました。