topcoderとCodeforcesとTOEICのレーティング別人数比をグラフ化する (Pandas+seaborn)

各種レーティングの人数比を積み上げ棒グラフとして視覚化してみました。
Python (matplotlib + Pandas + seaborn) で描画しています。

途中まではグラフの描画方法なので、結果だけみたい方は[こちら](#example)へ飛んで下さい。


# 本記事の対象者

- Pythonでグラフを書きたい
- topcoder/Codeforcesに参加していて、自分の位置が気になってしょうがない
- TOEICを受けたけど、いまいちスコアの基準が掴めない
- 次の目標を決めたい
- 俺がビートマニアだ

# 動機と結果

- CodeforcesでのDiv.1の基準がtopcoderに比べて高い気がしたので調べてみた
    - かなり高かった（上位10%）
- TOEICの基準がよく分からなかったので調べてみた
    - 600点が中央だった
- seabornを使うと綺麗なグラフが書けるらしいので試してみた
    - 簡単綺麗、オススメ


# グラフの書き方
[pythonで美しいグラフ描画 -seabornを使えばデータ分析と可視化が捗る その1](http://qiita.com/hik0107/items/3dc541158fceb3156ee0)
こちらの記事を参考にさせていただきました。

基本的に、以下の流れでグラフを描画します。

1. ランク（レーティングの範囲）の定義を用意する
1. データソースから、各ユーザーのレーティングを要素とした配列を生成する（または手作業）
1. Pandasのデータフレームを作成する
1. 積み上げ棒グラフを描画（画像として保存）する

## ランクの定義を用意する
topcoder、Codeforcesのレーティングシステムでは、色が重要な役割を果たしています。
そのため、「レーティングの範囲」「そのランクの名前」「色」という3要素を1つのランクとして定義した配列を用意し、グラフ描画に利用します。

例えば、topcoderでは以下のような定義を用意しました。

```py
# (border, name, color)
rank_info = [
    (0-1, 'Gray', '#9D9FA0'),
    (900-1, 'Green', '#69C329'),
    (1200-1, 'Blue', '#616BD5'),
    (1500-1, 'Yellow', '#FCD617'),
    (2200-1, 'Red', '#EF3A3A'),
    (3000-1, 'Target', '#000000'),
]
```

左端は、そのランクの下限-1の値を記載しています。
つまり、Grayは(-1..899]、Greenは(899..1199]、...という範囲になります。
後にpandas.cutでデータを加工する際に都合が良いため、このような定義にしています。
残りはランクの名前、色情報が並んでいます。

グラフを描画する際にはランク名のみや色情報のみの配列が必要になるため、適宜mapを使って切り出す必要があります。

```py
rank_borders = map(lambda x: x[0], rank_info)
names = map(lambda x: x[1], rank_info)
color = map(lambda x: x[2], rank_info)
```

## データソースからレーティングを要素とした配列を生成
データソースは様々な形式があります。
JSON、XMLなど比較的整った形式が取られているものから、Webページやツイートまで様々です。
最近ではWebAPI経由での取得が多いかと思いますので、ここではCodeforcesの例のみ示します。

```py
url = 'http://codeforces.com/api/user.ratedList?activeOnly=true'
response = urllib2.urlopen(url).read()
data = json.loads(response)

if data['status'] == 'OK':
    res = data['result']

    ratings = map(lambda x: x['rating'], res)
```

最終的に、ratingsというレーティングの数値が詰まった配列を用意しています。
他のデータソースについても、パーサーを用意したり、値を手動で用意したり、それぞれの形式にあった方法で取得します。


## Pandasのデータフレームを作成する
データフレームという、分析や描画機能の充実したクラスにデータを入れます。
describe関数でデータのサマリーが見れたりと非常に便利です。

早速作りたいところですが、今回データソースから抽出したデータは、以下の様なレーティングの値を要素に持つ配列です。
[1600, 2700, 100, 1200, ... ]

目的のデータを描画するため、以下の様なランク値ごと割合を表す配列に変換する必要があります。
[0.1, 0.15, 0.3, 0.25, ... ]
（ランク0が10%、ランク1が15%、...）

変換には様々な方法があると思いますが、今回は以下の手順を取りました。

1. [1600, 2700, 100, 1200, ... ]　（レーティングの配列）
2. [3, 8, 0, 1, ... ] （ランクの配列）
3. { 0: 300, 1: 800, 2: 2000, 3: 12000, ... } （ランクごとのCounter）
4. [300, 800, 2000, 1200, ... ] （ラングごとの人数）
5. [0.1, 0.15, 0.3, 0.25, ... ] （ランクごとの割合）

### 変換の詳細
1->2の変換は、もちろん自分でループを書いて変換しても良いですが、
このような処理を簡単に行える[pandas.cut](http://pandas.pydata.org/pandas-docs/version/0.17.1/generated/pandas.cut.html)という関数があります。

[a, b, c, d] というランクの基準を表す配列を用意しておくと、
(a, b]の間の値は0、(b, c]の間は1、(c, d]の間は2、
という変換を行ってくれます。

注意点として、今回のデータ定義では一番上のランクの人がカウントされなくなってしまうので、末尾に番兵として大きな値（INT_MAXとか）を追加してあげる必要があります。

また、2->3、3->4の変換は、Pythonの[Counter](http://docs.python.jp/2/library/collections.html)を使うと簡単に行なえます。

```py
import pandas as pd

# 1->2->3
bins = rank_borders + [sys.maxint]
ranks = Counter(pd.cut(ratings, bins, labels=range(len(rank_info)))).items()

# 3->4
num_list = map(lambda x: x[1], ranks)
```

最後に、データフレームを作りつつ、4->5の変換を行います。
divに合計を与えることで、それぞれを割合に変換してくれます。
グラフ描画の都合上、最後に.Tを付けて転置しています。

```py
df = pd.DataFrame(num_list, columns=[''], index=rank_list).div(len(ratings)).T
```


## 積み上げ棒グラフを描画（画像として保存）する
ランクごとの割合が得られたら、あとは描画するだけです。
データフレーム単品で描画を行うことが出来ます。

```py
df.plot(kind='bar', stacked=True)
```

seabornをimportしていれば、既にmatplotlibの無機質なグラフからおさらばしています。
ただし、このままでは少し見た目的に良くない箇所がありますので、以下のように調整を行っています。

```py
import seaborn as sns

sns.set_context('talk', 1.2)  # 文字サイズを大きく
sns.set_palette(color)        # ランクの色を設定

# 凡例の順番を逆にする
handles, labels = sns.plt.gca().get_legend_handles_labels()
sns.plt.gca().legend(reversed(handles), reversed(labels), loc='lower left')

# 上限を1.0にして、余白を無くす
sns.plt.yticks(np.arange(0.0, 1.1, 0.1))  # 0.1刻みで表示。1.0までにすると、1.0は含まない
sns.plt.ylim(0.0, 1.0)  # こちらで1.0まで描画にする
```

最後に、show()で画面に表示させるか、savefigで画像として保存します。

```py
sns.plt.show()  # 画面表示
# sns.plt.savefig("image.png")  # 画像保存
```

以上の手順を組み合わせると、グラフが描画できます。


<a name="example"></a>
# 実際の描画例

## Topcoder
[APIが用意されており](http://docs.tcapi.apiary.io/#reference/software-challenges/top-ranked-members)、ご丁寧にサンプルまであるのですが、algorithmのTop Ranked Membersにアクセスすると400が返ってきました。

```json
  "error": {
    "name": "Not Found",
    "value": 404,
    "description": "The URI requested is invalid or the requested resource does not exist.",
    "details": "No results found"
  }
```

ちなみに、contestTypeに適当な文字列を入れると候補を返してくれます。
ただし、半数は400が返ってきます。

```json
  "error": {
    "name": "Bad Request",
    "value": 400,
    "description": "The request was invalid. An accompanying message will explain why.",
    "details": "challengeType should be an element of design,development,specification,architecture,bug_hunt,test_suites,assembly,ui_prototypes,conceptualization,ria_build,ria_component,test_scenarios,copilot_posting,content_creation,reporting,marathon_match,first2finish,code,algorithm."
  }
```

致し方ないので、下記のXMLデータから抽出しました。
http://apps.topcoder.com/wiki/display/tc/Algorithm+Data+Feeds

データは2種類あり、アクティブユーザー（180日以内にレートの付くコンテストに参加した）と全ユーザーがあります。

### グラフ
- アクティブユーザー（5469人）
![active.png](https://qiita-image-store.s3.amazonaws.com/0/49432/b83f8dcf-5650-95fa-c567-de22d75ef388.png)
- 全ユーザー（69096人）
![all.png](https://qiita-image-store.s3.amazonaws.com/0/49432/d17e574d-1330-3c8d-431d-36caa1ff3e07.png)

### アクティブユーザーの考察
まず、思ったよりアクティブユーザー数が少ないのが気になります。
比率を見ると、全体のほぼ6割がDiv.2、4割がDiv.1と良い分かれ方をしています。
少人数のアクティブユーザー、かつ（登録が大変なので）サブ垢も少ないことにより、レーティングの計算式に綺麗に沿っているのではないかと考えられます。

黄色はおおよそ上位20%からになっており、レッドコーダーは上位3.9%でした。
ターゲットは0.29%で、グラフ上では目視で確認できるギリギリの太さになっています。
目が疲れていると見逃しそうです。
まさに雲の上の人ですね。

### 一行まとめ
topcoderは比率がきれい。


## Codeforces
前述したように、[APIが用意されています](http://codeforces.com/api/help)。
user.ratedListを要求すると一覧がJSON形式で返ってきます。
こちらも、アクティブユーザーと全ユーザーが分かれていますが、アクティブの条件は「先月レートの付くコンテストに参加したか」というもので、topcoderより厳しい条件となっているようです。

### グラフ
- アクティブユーザー（9640人）
![active.png](https://qiita-image-store.s3.amazonaws.com/0/49432/a15e2ce8-be89-001a-c234-db9c7b52132a.png)
- 全ユーザー（71606人）
![all.png](https://qiita-image-store.s3.amazonaws.com/0/49432/16ab2c84-55a4-bcd8-f4fb-7715a93ac911.png)


### 考察
サブ垢が一定数いる気がしますが、それを除外して考えてもアクティブユーザーが多いですね。
条件が厳しいのにこの人数なのは勢いを感じます。

体感通り、topcoderに比べてDiv.1の基準は相当高く、上位10%でした（紫～）。
topcoderに比べて初心者の参入が多いのか、時間帯的に世界中から参加しているため上位が際立っているのかもしれません。
また、紫より上は2.4%しかいないのですが、そこにランクが5種類もあります。
初心者のモチベーション的にも、もう少し下に分けるべきではないかと思われます。

### 一行まとめ
上位陣の神々の遊び（そんなにランク分け要る…？）


## TOEIC
以下のWebページから、現在最新である206回のデータを取得しました。
http://www.toeic.or.jp/toeic/about/data/data_avelist/data_dist01_09.html
既に比率が書かれている親切なデータです。
グラフ化には使いませんでしたが。

なお、リスニング・リーディング・合計の3種類でそれぞれグラフ化しています。


### グラフ
- Listening, Reading（94782人）
![toeic.png](https://qiita-image-store.s3.amazonaws.com/0/49432/586e4f69-ee7b-5035-7b4a-92932dbd750c.png)
- Total （94782人）
![total.png](https://qiita-image-store.s3.amazonaws.com/0/49432/439a9ce5-5013-b4c4-82ea-bbe999386c01.png)



### 考察
参加者多いですね。
競プロも毎回これくらいの参加者がいれば……。サーバーが落ちる！
競プロ界の発展を祈るのみです。

グラフはおおよそ50点刻みですが、綺麗な分布になってします。
詳しい採点方法は分からないですが、点数->分布ではなく、分布->点数が決まるそうなので当然です。

ただ、ListeningよりReadingの方が中間が均一で、上位が少なくなっているように見受けられます。
Listeningの470～は4.1%に対し、Readingの470～は1.1%となっています。
確かに、よくReadingの方が点数が低いという話を聞きます。
Listeningは聞き取れる人にとっては満点確実ですが、Readingは時間的制約もありそうもいかないのが原因でしょうか。

合計に目を向けると、145点はおおよそ20%の位置で、そこから50点ごとに10%ずつ増えているような形になっています。
600点は50%の位置ですね。

書店でよく見る「990点」、要は満点の人数が知りたいところですが、データが公開されていないのでわかりませんでした。
少なくとも、上位3.6%には入っているようです。
もちろんこの回のみの話ではあります。

### 一行まとめ
50点の差は意外と大きかった。


## IIDXのSP段位
**オマケです**

レーティングといえば、Beatmania IIDXの段位を置いて他にはないでしょう（個人の感想です）。
気になる人も多いのか、以下の様な先行研究があります。
http://clickagain.sakura.ne.jp/top/tokusyuu/dani_dd/dani_tokusyuu2.html
http://esports-runner.com/beatmaniaiidx/dani_transition2/

というわけで、グラフ化してみました。
最新作（Copula）の現在値と、前作（Pendual）の最終結果の2種類で作成しています。
なお、SP段位のみです。

最新作での人数は、Twitterで@2500bpmさんが毎日つぶやいていますので、それを利用しました。
https://twitter.com/2500bpm
前作の結果は公式サイトにあるので、それぞれの段位での人数を数えて抽出しています。
http://p.eagate.573.jp/game/2dx/22/p/ranking/dani.html

### グラフ
- 最新作（55075人）
![iidx.png](https://qiita-image-store.s3.amazonaws.com/0/49432/0afac32b-b401-5fcb-c316-ee3b57e6bcb0.png)
- 前作（89599人）
![pendual.png](https://qiita-image-store.s3.amazonaws.com/0/49432/12844fcd-d0d0-85cd-ff52-da7ea461aac2.png)

### 考察
大体のユーザーが知っている通り、下級段位の過疎っぷりが凄いですね。
四段までは圧縮されています。
今作はまだ九段以降を受けていない人がおおいのか、八段の人数が多いようです。
これは稼働が続くに連れ、比率は徐々に減っていくのではないかと思われます。
前作と比べると、中伝がうまく十段の団子を緩和している事がわかります。

### 一行まとめ
バランス……。

## BMSのSP段位
ついでに、BMSの方も見てみましょう。
[LR2IR](http://www.dream-pro.info/~lavalse/LR2IR/search.cgi?mode=gradelist)で人数が公開されているので、こちらを利用しました。
BMSはその特性上、不正やサブ垢もあるかとは思いますので、参考程度のデータです。

全段位と、発狂段位のみの2種類を作成しました。

### グラフ
- 全段位（58166人）
![bms.png](https://qiita-image-store.s3.amazonaws.com/0/49432/fa3b473e-0ab1-a889-1fad-210e7955aee9.png)
- 発狂段位のみ（29584人）
![insane.png](https://qiita-image-store.s3.amazonaws.com/0/49432/488aff50-2525-3c0a-13f0-36d8836d78eb.png)

### 考察
全段位の方は、意外と均一に分布しています。
様々な層のユーザーがいるのかもしれませんし、段位間のバランスが良いのかもしれません。
発狂に入ってしまった人数はおおよそ半数になっています。
発狂から始める人が多いかとおもいきや、しっかりと通常段位も遊ばれているようです。

発狂段位は、おおよそ難易度が上がるごとに人数が減っていく分布になっています。
★05の人数が少ないことを見ると、★06は受かりやすいのかもしれません。

最高段位である(^^)の割合は0.07%。
★★の割合は1.2%。
オススメはしませんが、興味のある人は動画を見てみると良いかもしれません。

### 一行まとめ
(^^)

# レーティング間の比較
異なる競技の業界内権威を比べてみます。
ランクは以下のように定めました。
- 上位80%以内: 初級者
- 上位40%以内: 中級者
- 上位20%以内: 上級者
- 上位10%以内: 超上級者
- 上位5%以内: ランカー
- 上位1%以内: 神

それぞれ異なる基準を持つので比べてもしょうがないですが、
おおよそ他のレーティングの感じが掴めるかもしれません。
あくまで目安程度のものであり、正確性は一切保証しません。


## 上位80%以内: 初級者
- topcoder: 灰色中位コーダー
- Codeforces: Pupil（薄い緑）下位
- TOEIC: 140
- IIDX: 一級
- BMS: ☆06

## 上位40%以内: 中級者
- topcoder: 青色下位コーダー
- Codeforces: Specialist（濃い緑）中位
- TOEIC: 645
- IIDX: 二段～三段
- BMS: ★01最上位～★02底辺

## 上位20%以内: 上級者
- topcoder: 青色最上位～黄色底辺コーダー
- Codeforces: Expert（青色）中位
- TOEIC: 745
- IIDX: 十段中位
- BMS: ★04中位

## 上位10%以内: 超上級者
- topcoder: 黄色中位コーダー
- Codeforces: Expert（青色）最上位～Candidate Master（紫）底辺
- TOEIC: 820
- IIDX: 中伝中位
- BMS: ★06

## 上位5%以内: ランカー
- topcoder: 黄色最上位～赤コーダー
- Codeforces: Candidate Master（紫）中～上位
- TOEIC: 895
- IIDX: 皆伝
- BMS: ★08

## 上位1%以内: 神
- topcoder: 赤上位～Target
- Codeforces: Grandmaster（橙色）～ Legendary grandmaster（濃い赤）
- TOEIC: 895～990
- IIDX: 皆伝上位～??
- BMS: ★09上位～(^^)


# まとめ
Python + matplotlib + Pandas + seabornを使うと、上記の通り簡単に綺麗なグラフを書くことができます。
もちろんnumpy、scipyが使えるので、詳細な分析も行えます。
何か気になることがあれば、さくっとグラフ化してみてはいかがでしょうか。