昨日の記事でニコニコ動画の動画検索の結果を取得できるようになりました
ニコニコ動画 『スナップショット検索API』 に触ってみた - 唯物是真 @Scaled_Wurm
このデータを使って東方キャラ同士の関連性の強さを測ってみます
PMI
PMI(Pointwise Mutual Information)という指標で関連性の強さを測ります
前に別の記事でもちょっと話題にしましたが、これは共起性(ある2つのことが一緒に発生しやすいか)を調べるための指標の一つです
共起性の指標には他にもいろいろあって下記の記事が参考になります
なぜ単純に2つのことが同時に発生した頻度の大小を比較しないかというと、例えばそれぞれ起こりやすい2つの事象が偶然同時に起こる頻度が大きくなることがあるからです
式としては次のようになっています(Wikipediaの式を参考)
$$\operatorname{pmi}(x;y) \equiv \log\frac{p(x,y)}{p(x)p(y)} = \log\frac{p(x|y)}{p(x)} = \log\frac{p(y|x)}{p(y)}$$ 2つの事象\(x\)と\(y\)についてのPMIは\(x\)と\(y\)がそれぞれ起こる確率\(p(x), p(y)\)と、\(x\)と\(y\)が同時に発生する確率\(p(x, y)\)で表されます
事象\(x\)と\(y\)が独立(同時に起こりやすい、起こりにくいなどの関係を持たない)場合\(p(x,y)=p(x)p(y)\)となるので、\(\log\frac{p(x,y)}{p(x)p(y)}\)は独立な場合と比較した時の起こりやすさの比の対数をとったものになっています
PMIは\(-\infty\)のような扱いづらい値になりうるので、-1から1に正規化したバージョンのNormalized PMI(NPMI)を今回は使います
$$\operatorname{npmi}(x;y) = \frac{\operatorname{pmi}(x;y)}{-\log \left[ p(x, y) \right] }$$
東方キャラの関連性の強さをNPMIで測る
昨日の記事でも使いましたが以下の記事から手作業で抽出したキャラのリストを使います
東方Projectの登場キャラクターとは (トウホウプロジェクトノトウジョウキャラクターとは) [単語記事] - ニコニコ大百科
東方を含む動画の中からある一人のキャラを含む確率と、ある二人のキャラを含む確率を使ってNPMIを計算します
PMIは頻度が少ないものに対しては大きくなる傾向があるので、100件の動画以上に含まれるキャラに限定して実行します
結果
上位30件を示します
そこそこ関係性の強いペアが取れていて、よい結果が得られていると思います
NPMI | キャラ名1 | キャラ名2 | 両方のキャラを含む動画数 | キャラ1を含む動画数 | キャラ2を含む動画数 |
---|---|---|---|---|---|
0.931126459724 | マエリベリー・ハーン | 宇佐見蓮子 | 87 | 103 | 112 |
0.650891211299 | 八坂神奈子 | 洩矢諏訪子 | 43 | 130 | 154 |
0.631734271825 | 火焔猫燐 | 霊烏路空 | 40 | 137 | 148 |
0.602341571226 | 姫海棠はたて | 射命丸文 | 49 | 102 | 337 |
0.59609987972 | 姫海棠はたて | 犬走椛 | 32 | 102 | 178 |
0.590804045548 | ナズーリン | 寅丸星 | 44 | 210 | 149 |
0.588793784001 | 古明地こいし | 古明地さとり | 120 | 411 | 380 |
0.57607555259 | 茨木華扇 | 霍青娥 | 22 | 116 | 100 |
0.566549459188 | 射命丸文 | 犬走椛 | 60 | 337 | 178 |
0.546457926202 | チルノ | 大妖精 | 159 | 1090 | 283 |
0.541293449936 | 十六夜咲夜 | 紅美鈴 | 187 | 830 | 491 |
0.528101230505 | 八雲藍 | 橙 | 71 | 292 | 341 |
0.522984039421 | フランドール・スカーレット | レミリア・スカーレット | 163 | 561 | 650 |
0.502878552921 | 八意永琳 | 鈴仙・優曇華院・イナバ | 21 | 117 | 160 |
0.495110667871 | 上白沢慧音 | 藤原妹紅 | 42 | 208 | 269 |
0.487466019296 | 聖白蓮 | 雲居一輪 | 24 | 249 | 103 |
0.484447101257 | 八意永琳 | 蓬莱山輝夜 | 22 | 117 | 197 |
0.482048739037 | 星熊勇儀 | 水橋パルスィ | 29 | 228 | 155 |
0.477043462432 | 西行寺幽々子 | 魂魄妖夢 | 51 | 241 | 350 |
0.475368618885 | 博麗霊夢 | 霧雨魔理沙 | 249 | 1123 | 788 |
0.474838904611 | 星熊勇儀 | 茨木華扇 | 23 | 228 | 116 |
0.471282529366 | 物部布都 | 豊聡耳神子 | 35 | 230 | 219 |
0.470335114779 | 多々良小傘 | 東風谷早苗 | 55 | 191 | 516 |
0.463467329564 | 八坂神奈子 | 東風谷早苗 | 41 | 130 | 516 |
0.458571493885 | 東風谷早苗 | 洩矢諏訪子 | 45 | 516 | 154 |
0.454412940559 | 古明地さとり | 火焔猫燐 | 33 | 380 | 137 |
0.451740069099 | パチュリー・ノーレッジ | 小悪魔 | 80 | 383 | 501 |
0.451734260935 | 八雲紫 | 八雲藍 | 67 | 508 | 292 |
0.446079627968 | パチュリー・ノーレッジ | 紅美鈴 | 77 | 383 | 491 |
0.439212242507 | 蓬莱山輝夜 | 藤原妹紅 | 31 | 197 | 269 |
ソースコード
実験に使ったキャラ数や期間が違うので、上の結果とはちょっと違う値が出るかもしれません
何件以上の動画に出てきたかのしきい値が低いとキャラ数の組み合わせが多くなってしまい、検索APIを叩くのに1秒ずつ間を開けているので、データを取るのに数時間かかる場合があるので注意
# -*- coding: utf-8 -*- import math from NiconicoSnapshotAPIWrapper import * def npmi(px, py, pxy): if pxy == 0: return -1 if px == py == pxy: return 1 return -math.log(pxy / px / py) / math.log(pxy) if __name__ == '__main__': api = NiconicoSnapshotAPIWrapper('NiconicoSnapshotAPIWrapper')#アプリ名を入れてください freq = {} freq[u'東方'] = api.query(u'東方', size = 0, filters=[api.makeFilterRange('start_time', '2014-01-01 00:00:00', '2014-11-08 00:00:00')]).total data = u"""博麗霊夢 霧雨魔理沙 ルーミア 大妖精 チルノ 紅美鈴 小悪魔 パチュリー・ノーレッジ 十六夜咲夜 レミリア・スカーレット フランドール・スカーレット レティ・ホワイトロック 橙 アリス・マーガトロイド リリーホワイト ルナサ・プリズムリバー メルラン・プリズムリバー リリカ・プリズムリバー 魂魄妖夢 西行寺幽々子 八雲藍 八雲紫 伊吹萃香 リグル・ナイトバグ ミスティア・ローレライ 上白沢慧音 因幡てゐ 鈴仙・優曇華院・イナバ 八意永琳 蓬莱山輝夜 藤原妹紅 メディスン・メランコリー 風見幽香 小野塚小町 四季映姫・ヤマザナドゥ 射命丸文 秋静葉 秋穣子 鍵山雛 河城にとり 犬走椛 東風谷早苗 八坂神奈子 洩矢諏訪子 永江衣玖 比那名居天子 キスメ 黒谷ヤマメ 水橋パルスィ 星熊勇儀 古明地さとり 火焔猫燐 霊烏路空 古明地こいし ナズーリン 多々良小傘 雲居一輪 雲山 村紗水蜜 寅丸星 聖白蓮 封獣ぬえ 姫海棠はたて 幽谷響子 宮古芳香 霍青娥 蘇我屠自古 物部布都 豊聡耳神子 二ッ岩マミゾウ 秦こころ わかさぎ姫 赤蛮奇 今泉影狼 九十九弁々 九十九八橋 鬼人正邪 少名針妙丸 堀川雷鼓 綿月豊姫 綿月依姫 レイセン サニーミルク ルナチャイルド スターサファイア 茨木華扇 本居小鈴 森近霖之助 稗田阿求 宇佐見蓮子 マエリベリー・ハーン""".split() for word in data: time.sleep(1) freq[word] = api.query(u'東方 ' + word, retry = 3, size = 0, filters=[api.makeFilterRange('start_time', '2014-01-01 00:00:00', '2014-11-08 00:00:00')]).total cofreq = {} for word1 in data: for word2 in data: if word2 <= word1: continue time.sleep(1) cofreq[word1 + u' ' + word2] = api.query(u'東方 ' + word1 + ' ' + word2, retry = 5, size = 0, filters=[api.makeFilterRange('start_time', '2014-01-01 00:00:00', '2014-11-08 00:00:00')]).total TH = 100 #しきい値 import numpy as np result = [] for word1 in data: if freq[word1] < TH: continue px = float(freq[word1]) / freq[u'東方'] for word2 in data: if word2 <= word1 or freq[word2] < TH: continue py = float(freq[word2]) / freq[u'東方'] pxy = float(cofreq[word1 + u' ' + word2]) / freq[u'東方'] result.append((npmi(px, py, pxy), word1, word2, cofreq[word1 + u' ' + word2], freq[word1], freq[word2])) result.sort(reverse=True) for r in result[:10]: print r[0], r[1], r[2], r[3], r[4], r[5]