[Python]レビューソート問題をベイズ推論で解いてみる

# 概要

- [Pythonで体験するベイズ推論 PyMCによるMCMC入門](https://www.amazon.co.jp/Python%E3%81%A7%E4%BD%93%E9%A8%93%E3%81%99%E3%82%8B%E3%83%99%E3%82%A4%E3%82%BA%E6%8E%A8%E8%AB%96-PyMC%E3%81%AB%E3%82%88%E3%82%8BMCMC%E5%85%A5%E9%96%80-%E3%82%AD%E3%83%A3%E3%83%A1%E3%83%AD%E3%83%B3-%E3%83%87%E3%83%93%E3%83%83%E3%83%89%E3%82%BD%E3%83%B3-%E3%83%94%E3%83%AD%E3%83%B3/dp/4627077912/)の書籍を結構前に読了しましたが、しばらくPyMCを触っていないので、復習用に自習課題として動かします。
- どんな書籍なのか、というのはここでは触れないので、[『Pythonで体験するベイズ推論 ―PyMCによるMCMC入門―』の書評](http://statmodeling.hatenablog.com/entry/bayesian-methods-for-hackers-ja)などをご確認ください。（書籍自体はとても分かりやすかった・・）
- ちなみに統計・確率・数学・ベイズ周りは初心者（理系出身でもない）なため、理解が浅い点はご了承ください。

# 誰のための記事？

- 書籍の原書が元々for Hackers とあるように、書籍もエンジニア・もしくは最近PyMCを触り始めた方など向けで、この記事でもほとんど数式などは扱いません。
- がっつりデータサイエンスを仕事にしている方達からすると、大分物足りない記事だとは思います。
- 機械学習（特にディープラーニング）を先に学んでから、それを踏まえて比較などしつつ記事を書いています。「[ゼロから作る Deep Learning](https://www.amazon.co.jp/%E3%82%BC%E3%83%AD%E3%81%8B%E3%82%89%E4%BD%9C%E3%82%8BDeep-Learning-%E2%80%95Python%E3%81%A7%E5%AD%A6%E3%81%B6%E3%83%87%E3%82%A3%E3%83%BC%E3%83%97%E3%83%A9%E3%83%BC%E3%83%8B%E3%83%B3%E3%82%B0%E3%81%AE%E7%90%86%E8%AB%96%E3%81%A8%E5%AE%9F%E8%A3%85-%E6%96%8E%E8%97%A4-%E5%BA%B7%E6%AF%85/dp/4873117585/ref=sr_1_1?ie=UTF8&amp;qid=1522492306&amp;sr=8-1&amp;keywords=%E3%82%BC%E3%83%AD%E3%81%8B%E3%82%89%E4%BD%9C%E3%82%8Bdeep+learning)」など消化済みの方だと、より読みやすいかもしれません。

# 課題内容

- 書籍で例題として、upvote / downvote（いいね的な、親指マークなど）のソート問題がありました。
- また、そちらの例題での内容は、レビュー（Amazon的な）のソートとしても使えると書かれており、数式などが軽く触れられていましたが、実際にPyMCを動かして、といったところまでは書かれていなかったので、課題としてこのレビューのソート問題を扱います。

## 実現したいこと

- レビューが絡む要素（商品だったり、映画だったり、etc）のおすすめ順的なもののためのソートを実現する。

## 前提

- ☆1～☆5までの範囲で、ユーザーが商品に対してレビューを投稿できるようなサービス。

## 普通にソートすることによる問題

単純に、評価の高い順でソートすると、以下のような問題が出てくる。

- ☆5.0、レビュー数1・2件程度の商品が先頭に出てくる。
- 本当はユーザーからすると、☆4.8とかのレビューが50件程度ある要素にアクセスしたいと思われるため、できればそういった「本当に評価が高いと思われる」要素が先頭になるようにソートをしたい。
    - 直観的にも、☆5.0でレビューが1件しかない要素よりも、☆4.8でレビューが50件程度ある要素のほうが魅力的に感じる（Amazonの商品選択時などでも、そういった商品を自然と選んでいる）。

## 解決方法

- PyMCを使います。（ほかに色々やり方はあると思いますが、PyMCの復習目的のため）
- 書籍と同様に、MCMCでの処理後、レビュー内容的に「良い商品である可能性が高い」という商品が先頭に来るようにします。
    - 結果としてレビュー平均値が高く、且つレビュー数が多い（＝平均値が真の値である確率が高い）商品が優遇されるようになります。
    - レビュー数が多い方が確率が高くなる、というのはサイコロを多く振った方が各面が出る確率が真の値に近づいていくことをイメージすると分かりやすい・・かもしれません。
- ソート自体はスカラー値で扱う必要があります。ここでは、書籍と同様、95％信用下限（後で触れます）を使います。

## この課題で扱わないこと

- 計算をより短時間で終わらす（webサービスなどで大量にリクエストをさばくケースなどための）ための近似対応などは、書籍では扱われていたものの色々数式の話など必要になるのでここでは省略します。
- 古い商品の方が、レビュー数が多くなりやすく有利になる、という点も書籍で触れられていましたが、この記事では話をシンプルにするために触れません。

# ダミーデータの準備

課題で使うための、ダミーデータを乱数などで用意します。

- 200件の商品データを想定。
- ID、レビュー数、レビュー比率（1.0～5.0）のカラムのデータフレームで扱います。


```python
import pandas as pd
```


```python
import numpy as np
```


```python
import seaborn as sns
```


```python
sns.set_style(style='darkgrid')
```


```python
product_df = pd.DataFrame(
    columns=['id', 'review_num', 'review_rate'],
    index=np.arange(0, 200))
```


```python
product_df.id = product_df.index + 1
```


```python
review_num_arr = np.random.lognormal(
    mean=2, size=(200,)) + 1
```


```python
product_df.review_num = review_num_arr
```


```python
product_df.review_num = product_df.review_num.astype(np.int, copy=False)
```


```python
product_df.review_rate = np.random.uniform(
    low=1.0, high=5.0, size=(200,))
```


```python
product_df[:10]
```




<div>
<table class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>id</th>
      <th>review_num</th>
      <th>review_rate</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>1</td>
      <td>29</td>
      <td>1.171052</td>
    </tr>
    <tr>
      <th>1</th>
      <td>2</td>
      <td>8</td>
      <td>4.512538</td>
    </tr>
    <tr>
      <th>2</th>
      <td>3</td>
      <td>12</td>
      <td>4.310933</td>
    </tr>
    <tr>
      <th>3</th>
      <td>4</td>
      <td>17</td>
      <td>1.410750</td>
    </tr>
    <tr>
      <th>4</th>
      <td>5</td>
      <td>37</td>
      <td>3.264369</td>
    </tr>
    <tr>
      <th>5</th>
      <td>6</td>
      <td>3</td>
      <td>4.899745</td>
    </tr>
    <tr>
      <th>6</th>
      <td>7</td>
      <td>7</td>
      <td>2.256719</td>
    </tr>
    <tr>
      <th>7</th>
      <td>8</td>
      <td>1</td>
      <td>2.349467</td>
    </tr>
    <tr>
      <th>8</th>
      <td>9</td>
      <td>1</td>
      <td>4.490342</td>
    </tr>
    <tr>
      <th>9</th>
      <td>10</td>
      <td>5</td>
      <td>1.742555</td>
    </tr>
  </tbody>
</table>
</div>




```python
product_df[190:200]
```




<div>
<table class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>id</th>
      <th>review_num</th>
      <th>review_rate</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>190</th>
      <td>191</td>
      <td>14</td>
      <td>2.171503</td>
    </tr>
    <tr>
      <th>191</th>
      <td>192</td>
      <td>5</td>
      <td>4.223269</td>
    </tr>
    <tr>
      <th>192</th>
      <td>193</td>
      <td>29</td>
      <td>1.224605</td>
    </tr>
    <tr>
      <th>193</th>
      <td>194</td>
      <td>3</td>
      <td>2.388260</td>
    </tr>
    <tr>
      <th>194</th>
      <td>195</td>
      <td>29</td>
      <td>2.887370</td>
    </tr>
    <tr>
      <th>195</th>
      <td>196</td>
      <td>4</td>
      <td>4.222529</td>
    </tr>
    <tr>
      <th>196</th>
      <td>197</td>
      <td>3</td>
      <td>2.737241</td>
    </tr>
    <tr>
      <th>197</th>
      <td>198</td>
      <td>16</td>
      <td>2.229572</td>
    </tr>
    <tr>
      <th>198</th>
      <td>199</td>
      <td>22</td>
      <td>1.797979</td>
    </tr>
    <tr>
      <th>199</th>
      <td>200</td>
      <td>5</td>
      <td>4.863885</td>
    </tr>
  </tbody>
</table>
</div>



レビュー数に関しては、少ない商品の方が多いだろう・・ということで以下にようになっています。


```python
product_df.plot(
    x='review_rate', y='review_num', kind='hist', bins=15)
```




    <matplotlib.axes._subplots.AxesSubplot at 0x7f77a20334a8>



![output_23_1.png](https://qiita-image-store.s3.amazonaws.com/0/228778/1e18fee3-ff54-52f2-6533-d0e76a120172.png)



```python
product_df.review_num.min()
```




    1




```python
product_df.review_num.max()
```




    111




```python
# 生成したデータを保存。
product_df.to_csv(
    './20180331_ベイズ推論_レビューソート問題_ダミーデータ.csv',
    index=False, encoding='utf-8')
```


```python
# 作業再開用。以前保存したデータを読み込む。
product_df = pd.read_csv('./20180331_ベイズ推論_レビューソート問題_ダミーデータ.csv')
```

# PyMCでモデルを組む

モデル自体はそこまで複雑にならないものの、説明を結構飛ばすため、分かりづらい点などはご了承ください。（詳しくは書籍を・・）

## ステップ概要

PyMCを扱う上で、いくつかのステップがあります。

### 確率変数や事前分布などをどうするのか決める。

どのような変数が必要になるのかを決定します。

ディープラーニングでいうところの入力値（画像認識であれば画像のテンソルなど）的なところになります。

今回は、レビュー数とレビューの平均値が変数となります。

また、事前分布と事後分布という単語が出てきます。事前分布は単純に一様分布を扱うこともあれば、信念（きっと結果は〇〇以下にはならない筈だ、専門家は大体××と考えている）に基づいて初期分布が与えることもできます。（もしくは、別のベイズモデルの結果の分布を与えたりすることもあるようです。（なんとなくStackGANっぽい））

一様分布のほうが客観的な処理となるのに比べ、事前分布になんらかの信念を与えたほうが真の値に近づくこともあります。逆に信念を与えることによって、結果にバイアスがかかり真の値からずれることもある、と言われています。どうすのかはケースバイケースで選択する必要がある、といったようなことが書籍では書かれています。

ディープラーニングでの敵対的生成モデル（GANs）などでいうと、一様分布を与えるのが普通のGANsモデルで、何らかの画像を生み出したりするのに対し、信念を与えるのは条件付き生成モデル（cGAN）とかに近く、たとえば線画を与えて、その線画に収まる形でコントロールされた状態で着色をしたり、といったものに近いかなあ・・と感じています。

今回は、レビュー評価に対して、一様分布を用いて進めます。

Youtubeだったりの単純なプラス評価とマイナス評価であれば二項分布（Binomialクラス）を与えますが、今回はレビュー評価が1.0～5.0の範囲の、連続型確率変数となります。

分かりやすいように、以下のように各レビューの値を0.0～1.0の範囲になるように定義します。

```math
レビュー評価_{1.0} = 0\\
レビュー評価_{2.0} = 0.25\\
レビュー評価_{3.0} = 0.5\\
レビュー評価_{4.0} = 0.75\\
レビュー評価_{5.0} = 1
```

0～1の範囲の連続型確率変数ということで、ベータ分布（Betaクラス）を使います。

このあたりの分布の話は、書籍だったり、[PyMCの初歩](http://wcs.hatenablog.com/entry/2017/05/28/222658)の記事で詳しく書かれています。一部、上記記事から引用させていただきます。

> $\theta$は0から1までの連続値であり、特に事前の情報を持たないので、 0から1までのすべてが等確率（密度）となる事前分布を使う。 これは具体的にはパラメタ$\alpha$と$\beta$がそれぞれ1のベータ分布である。
http://wcs.hatenablog.com/entry/2017/05/28/222658

上記のような条件で、商品1件分の事後分布を取得するスクリプトを考えてみます。（PyMCの基礎 の記事も参考にします）

このステップは、ディープラーニングでいうところの、学習中のステップに近いと言えます。

MCMC（マルコフ連鎖モンテカルロ法）の記述が出てきます。1ステップごとに前の結果を踏まえて次の探索を行う、といった処理になります。ディープラーニングで、前の結果を踏まえて、次のエポックの学習に移るのに似ています。

なお、その探索の移り変わりは、軌跡（trace）と呼ばれます。

## モデルを組む


```python
import pymc as pm
```


```python
def get_posterior_review_rate_trace(review_rate, review_num):
    """
    レビュー評価の事後分布の軌跡を取得する。
    
    Parameters
    ------------
    review_rate : float
        対象の商品の1.0 ～ 5.0の範囲のレビュー評価値。
    review_num : int
        対象の商品のレビュー数。
        
    Returns
    ---------
    trace : Trace
        レビュー評価の事後分布。
    """
    
    # 前述のとおり、レビューの評価は0～1の範囲の一様分布で扱うため、
    # 引数で与えられた値に対して調整を加えます。
    # name は確率変数名。軌跡にアクセスする際などに使うため、
    # 分かりやすい任意の文字列を指定します。
    _review_rate = pm.Uniform(name='review_rate', lower=0, upper=1)
    review_rate_0_to_1 = (review_rate - 1) * 0.25
    print('0～1の範囲でのレビュー評価 : ', review_rate_0_to_1)
    
    # 前述のとおり、連続確率変数のためBeta分布を使用します。
    theta = pm.Beta(name='theta', alpha=1, beta=1)
    
    # observations は事前の観測値だったり信念を加味した事前分布の値
    # （用意した確率変数など）を設定します。
    # n は回数となり、コインを投げた回数などをイメージすると分かりやすいき
    # かもしれません。この数が多いほど、結果の確信度が高まります。
    # 今回はレビュー数が該当します。
    # value は、コインを投げて何回表が出たのか、といった値になるため、
    # レビュー評価にレビュー数を乗算した値を指定しています。
    # observed は、既に事前に観測済みの値で、固定値ですよ、ということを
    # PyMCに指定している。今回、事前にレビュー評価などは分かっている（＝固定値 
    # なため、Trueを指定。
    observations = pm.Binomial(
        name='obs', n=review_num, p=theta,
        value=review_rate_0_to_1 * review_num,
        observed=True)
    
    # MAP（maximum a posterior）を使うという指定。
    # 収束に絡んだもので、基本的には使うと結果が良くなる
    # そうです。ここでは詳細な説明は省略。
    map_ = pm.MAP([theta, observations]).fit()
    
    # このあたりは、Kerasでモデルを組む時の感覚に似ている。
    model = pm.Model([theta, observations])
    mcmc = pm.MCMC(model)
    
    # MCMCの探索を行う。第一引数はサンプリング数（探索回数）。
    # ディープラーニングのエポックに近い。（1回辺りはすぐ終わるので、
    # ディープラーニングのエポックよりは数が多くなる）
    # 第二引数はバーンインと呼ばれ、ごの件数分はサンプリング結果が破棄
    # される。今回は5000件目～20000件目のサンプリング結果が取得される。
    # ディープラーニングで最初の方のエポックは安定感がない（精度の低い
    # 結果になる）のと同様、MCMCでも最初の方は収束しておらず好ましく
    # ない値になるので、一定数破棄する。
    mcmc.sample(20000, 5000)
    
    # 探索結果のthetaの確率変数の軌跡を取得する。
    trace = mcmc.trace('theta')
    return trace
```

## モデルの確認

組んだモデルが妥当かとうか、値を直接指定してみて結果を確認してみます。

事後分布がどんな感じになったのかの確認として、さくっと確認するためのPyMCのmatplotlibのラッパー的なモジュールが用意されているのでそちらを利用します。

matplotlibのpltと名称が被らないように、mcplotという名前で扱います。

取得した軌跡を引数に渡すだけで、事後分布がどのように分布になっているのかや95％信用区間を表示してくれます。


```python
from pymc.Matplot import plot as mcplot
```

レビュー評価は4.5でそれぞれ実施。

### レビューが少ないケース


```python
trace_of_review_num_2 = get_posterior_review_rate_trace(
    review_rate=4.5, review_num=2)
```

    
    0～1の範囲でのレビュー評価 :  0.875
     [-----------------100%-----------------] 20000 of 20000 complete in 1.5 sec


```python
mcplot(trace_of_review_num_2)
```

    Plotting theta



![output_41_1.png](https://qiita-image-store.s3.amazonaws.com/0/228778/4aeed548-267a-756e-5ad4-f2521cff202d.png)



右側が事後分布となります。

レビュー件数が裾野の広い事後分布となりました。件数が少ないので、値の正確度が低い（＝結構ばらつく）という結果になっています。また、真の値とも大分差が開いていることがわかります。

### レビューが多いケース


```python
trace_of_review_num_50 = get_posterior_review_rate_trace(review_rate=4.5, review_num=50)
```

    
    0～1の範囲でのレビュー評価 :  0.875
     [-----------------100%-----------------] 20000 of 20000 complete in 1.6 sec


```python
mcplot(trace_of_review_num_50)
```

    Plotting theta



![output_45_1.png](https://qiita-image-store.s3.amazonaws.com/0/228778/ff49b767-fac6-48f1-8f4e-0dad00ffb4a9.png)


先ほどのレビュー件数が少ない結果と比べて、大分裾野が狭くなりました。また、真の値が0.85（右のプロットの濃い縦線）辺りにあり、レビュー4.5の変換後の値0.875に近い値となっています。

レビュー件数が増えたことで、「この商品はそれなりに高い確率で0.85付近の評価が妥当そうだ」とか、95％信用区間（右のプロットの薄い縦の点線）的に「ほぼほぼの確率で0.75～0.93辺りのレビュー評価だろう」といったことが判断できます。

この95％信用下限（左側の縦の点線）の値を使い、ソートを行います。レビュー数が多いほど裾野が狭くなり、95％信用下限が高い値になる（＝ソートで有利に判定される）結果となります。

# 全商品の95％信用下限値の算出

組んだモデルを反映していきます。


```python
def get_lower_limit(trace):
    """
    対象の軌跡の95％信用下限を取得する。
    
    Parameters
    ------------
    trace : Trace
        対象の商品のMCMCの軌跡。
        
    Returns
    --------
    lower_limit : float
        対象の軌跡の95%信用下限値。
    """
    trace_arr = trace[:]
    N = trace_arr.shape[0]
    lower_limit = np.sort(trace_arr)[int(0.05 * N)]
    return lower_limit
```


```python
product_df['lower_limit'] = 0
```


```python
from datetime import datetime
```


```python
# 長時間かかります。
for index, sr in product_df.iterrows():
    print('\n', index + 1, '件目以降を実行中', datetime.now())
    
    review_rate = sr['review_rate']
    review_num = sr['review_num']
    
    trace = get_posterior_review_rate_trace(
        review_rate=review_rate, review_num=review_num)
    lower_limit = get_lower_limit(trace=trace)
    product_df.loc[index, 'lower_limit'] = lower_limit
```

```python
product_df[:5]
```




<div>
<table class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>id</th>
      <th>review_num</th>
      <th>review_rate</th>
      <th>lower_limit</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>1</td>
      <td>29</td>
      <td>1.171052</td>
      <td>0.013478</td>
    </tr>
    <tr>
      <th>1</th>
      <td>2</td>
      <td>8</td>
      <td>4.512538</td>
      <td>0.574099</td>
    </tr>
    <tr>
      <th>2</th>
      <td>3</td>
      <td>12</td>
      <td>4.310933</td>
      <td>0.495006</td>
    </tr>
    <tr>
      <th>3</th>
      <td>4</td>
      <td>17</td>
      <td>1.410750</td>
      <td>0.019257</td>
    </tr>
    <tr>
      <th>4</th>
      <td>5</td>
      <td>37</td>
      <td>3.264369</td>
      <td>0.403900</td>
    </tr>
  </tbody>
</table>
</div>



# ソート

各商品の95％信用下限が算出できたので、あとはソートして終了です。


```python
product_df.sort_values(by='lower_limit', ascending=False, inplace=True)
```


```python
def round_lower_limit(lower_limit):
    """
    95％信用下限の値が、桁数が多く見づらいので、浮動小数点の3桁で切り落とし
    (四捨五入)を行う。
    
    Parameters
    -------------
    lower_limit : float
        切り落とし対象の信用下限値。
    
    Returns
    ---------
    lower_limit : float
        切り落とし後の値。
    """
    return round(lower_limit, 3)
```


```python
product_df.lower_limit = product_df.lower_limit.apply(round_lower_limit)
```


```python
product_df[:50]
```




<div>
<table class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>id</th>
      <th>review_num</th>
      <th>review_rate</th>
      <th>lower_limit</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>159</th>
      <td>160</td>
      <td>19</td>
      <td>4.867595</td>
      <td>0.776</td>
    </tr>
    <tr>
      <th>125</th>
      <td>126</td>
      <td>44</td>
      <td>4.373100</td>
      <td>0.728</td>
    </tr>
    <tr>
      <th>24</th>
      <td>25</td>
      <td>14</td>
      <td>4.739080</td>
      <td>0.709</td>
    </tr>
    <tr>
      <th>55</th>
      <td>56</td>
      <td>12</td>
      <td>4.856710</td>
      <td>0.695</td>
    </tr>
    <tr>
      <th>174</th>
      <td>175</td>
      <td>17</td>
      <td>4.561806</td>
      <td>0.689</td>
    </tr>
    <tr>
      <th>102</th>
      <td>103</td>
      <td>12</td>
      <td>4.693846</td>
      <td>0.685</td>
    </tr>
    <tr>
      <th>77</th>
      <td>78</td>
      <td>11</td>
      <td>4.887662</td>
      <td>0.664</td>
    </tr>
    <tr>
      <th>42</th>
      <td>43</td>
      <td>11</td>
      <td>4.888299</td>
      <td>0.658</td>
    </tr>
    <tr>
      <th>175</th>
      <td>176</td>
      <td>10</td>
      <td>4.824883</td>
      <td>0.644</td>
    </tr>
    <tr>
      <th>126</th>
      <td>127</td>
      <td>18</td>
      <td>4.467957</td>
      <td>0.641</td>
    </tr>
    <tr>
      <th>166</th>
      <td>167</td>
      <td>42</td>
      <td>4.123655</td>
      <td>0.639</td>
    </tr>
    <tr>
      <th>26</th>
      <td>27</td>
      <td>31</td>
      <td>4.060111</td>
      <td>0.599</td>
    </tr>
    <tr>
      <th>1</th>
      <td>2</td>
      <td>8</td>
      <td>4.512538</td>
      <td>0.574</td>
    </tr>
    <tr>
      <th>153</th>
      <td>154</td>
      <td>8</td>
      <td>4.977846</td>
      <td>0.569</td>
    </tr>
    <tr>
      <th>169</th>
      <td>170</td>
      <td>14</td>
      <td>4.197506</td>
      <td>0.566</td>
    </tr>
    <tr>
      <th>79</th>
      <td>80</td>
      <td>14</td>
      <td>4.170081</td>
      <td>0.561</td>
    </tr>
    <tr>
      <th>118</th>
      <td>119</td>
      <td>11</td>
      <td>4.498642</td>
      <td>0.553</td>
    </tr>
    <tr>
      <th>144</th>
      <td>145</td>
      <td>14</td>
      <td>4.263963</td>
      <td>0.545</td>
    </tr>
    <tr>
      <th>158</th>
      <td>159</td>
      <td>35</td>
      <td>3.790759</td>
      <td>0.544</td>
    </tr>
    <tr>
      <th>168</th>
      <td>169</td>
      <td>16</td>
      <td>4.218747</td>
      <td>0.527</td>
    </tr>
    <tr>
      <th>110</th>
      <td>111</td>
      <td>7</td>
      <td>4.540376</td>
      <td>0.520</td>
    </tr>
    <tr>
      <th>187</th>
      <td>188</td>
      <td>15</td>
      <td>4.170675</td>
      <td>0.511</td>
    </tr>
    <tr>
      <th>122</th>
      <td>123</td>
      <td>9</td>
      <td>4.511826</td>
      <td>0.498</td>
    </tr>
    <tr>
      <th>2</th>
      <td>3</td>
      <td>12</td>
      <td>4.310933</td>
      <td>0.495</td>
    </tr>
    <tr>
      <th>184</th>
      <td>185</td>
      <td>6</td>
      <td>4.833444</td>
      <td>0.486</td>
    </tr>
    <tr>
      <th>124</th>
      <td>125</td>
      <td>6</td>
      <td>4.481689</td>
      <td>0.478</td>
    </tr>
    <tr>
      <th>78</th>
      <td>79</td>
      <td>6</td>
      <td>4.352680</td>
      <td>0.473</td>
    </tr>
    <tr>
      <th>150</th>
      <td>151</td>
      <td>6</td>
      <td>4.937529</td>
      <td>0.469</td>
    </tr>
    <tr>
      <th>73</th>
      <td>74</td>
      <td>8</td>
      <td>4.124395</td>
      <td>0.462</td>
    </tr>
    <tr>
      <th>63</th>
      <td>64</td>
      <td>15</td>
      <td>3.713055</td>
      <td>0.455</td>
    </tr>
    <tr>
      <th>84</th>
      <td>85</td>
      <td>8</td>
      <td>4.038797</td>
      <td>0.451</td>
    </tr>
    <tr>
      <th>89</th>
      <td>90</td>
      <td>21</td>
      <td>3.575696</td>
      <td>0.449</td>
    </tr>
    <tr>
      <th>131</th>
      <td>132</td>
      <td>12</td>
      <td>3.883360</td>
      <td>0.430</td>
    </tr>
    <tr>
      <th>148</th>
      <td>149</td>
      <td>14</td>
      <td>3.847961</td>
      <td>0.425</td>
    </tr>
    <tr>
      <th>199</th>
      <td>200</td>
      <td>5</td>
      <td>4.863885</td>
      <td>0.424</td>
    </tr>
    <tr>
      <th>191</th>
      <td>192</td>
      <td>5</td>
      <td>4.223269</td>
      <td>0.422</td>
    </tr>
    <tr>
      <th>173</th>
      <td>174</td>
      <td>12</td>
      <td>3.862418</td>
      <td>0.418</td>
    </tr>
    <tr>
      <th>134</th>
      <td>135</td>
      <td>5</td>
      <td>4.349610</td>
      <td>0.418</td>
    </tr>
    <tr>
      <th>104</th>
      <td>105</td>
      <td>16</td>
      <td>3.613496</td>
      <td>0.417</td>
    </tr>
    <tr>
      <th>141</th>
      <td>142</td>
      <td>7</td>
      <td>4.014073</td>
      <td>0.414</td>
    </tr>
    <tr>
      <th>186</th>
      <td>187</td>
      <td>7</td>
      <td>4.356096</td>
      <td>0.413</td>
    </tr>
    <tr>
      <th>29</th>
      <td>30</td>
      <td>7</td>
      <td>4.265361</td>
      <td>0.411</td>
    </tr>
    <tr>
      <th>94</th>
      <td>95</td>
      <td>11</td>
      <td>3.728870</td>
      <td>0.411</td>
    </tr>
    <tr>
      <th>17</th>
      <td>18</td>
      <td>13</td>
      <td>3.705521</td>
      <td>0.404</td>
    </tr>
    <tr>
      <th>39</th>
      <td>40</td>
      <td>9</td>
      <td>3.876715</td>
      <td>0.404</td>
    </tr>
    <tr>
      <th>4</th>
      <td>5</td>
      <td>37</td>
      <td>3.264369</td>
      <td>0.404</td>
    </tr>
    <tr>
      <th>67</th>
      <td>68</td>
      <td>11</td>
      <td>3.752770</td>
      <td>0.397</td>
    </tr>
    <tr>
      <th>52</th>
      <td>53</td>
      <td>9</td>
      <td>3.861583</td>
      <td>0.395</td>
    </tr>
    <tr>
      <th>40</th>
      <td>41</td>
      <td>11</td>
      <td>3.850809</td>
      <td>0.395</td>
    </tr>
    <tr>
      <th>93</th>
      <td>94</td>
      <td>111</td>
      <td>2.846798</td>
      <td>0.384</td>
    </tr>
  </tbody>
</table>
</div>



ある程度ランダムな分布に依存するため、ID2と154と行など少し気になるものの、大まかには課題としたソート結果を得ることができました。

# Appendix

- [使ったノート](https://github.com/simon-ritchie/note/blob/master/20180331_review_sort_problem/20180331_%E3%83%99%E3%82%A4%E3%82%BA%E6%8E%A8%E8%AB%96_%E3%83%AC%E3%83%93%E3%83%A5%E3%83%BC%E3%82%BD%E3%83%BC%E3%83%88%E5%95%8F%E9%A1%8C.ipynb)
- [生成した乱数のCSV](https://github.com/simon-ritchie/note/blob/master/20180331_review_sort_problem/20180331_%E3%83%99%E3%82%A4%E3%82%BA%E6%8E%A8%E8%AB%96_%E3%83%AC%E3%83%93%E3%83%A5%E3%83%BC%E3%82%BD%E3%83%BC%E3%83%88%E5%95%8F%E9%A1%8C_%E3%83%80%E3%83%9F%E3%83%BC%E3%83%87%E3%83%BC%E3%82%BF.csv)



# 環境

## ノート環境

- Azure Notebooks

## Python 環境


```python
!python -V
```

    Python 3.5.4 :: Anaconda custom (64-bit)


※PyMCのインポートでエラーになり、解決策で必要そうなapt-get関係のコマンドがAzure Notebooks関係で探すのが手間だったため、Python3.6ではなく3.5をカーネルに選択。

## ライブラリ環境


Azure Notebooksのデフォルトのライブラリそのままです。

```python
!pip freeze
```

    adal==0.5.0
    alabaster==0.7.10
    altair==1.2.1
    anaconda-client==1.6.5
    anaconda-navigator==1.6.9
    anaconda-project==0.8.0
    applicationinsights==0.11.1
    argcomplete==1.9.4
    arrow==0.12.1
    asn1crypto==0.22.0
    astroid==1.5.3
    astropy==2.0.2
    attrs==17.4.0
    Automat==0.6.0
    azure-batch==4.0.0
    azure-batch-extensions==1.0.1
    azure-cli==2.0.28
    azure-cli-acr==2.0.21
    azure-cli-acs==2.0.27
    azure-cli-advisor==0.1.2
    azure-cli-appservice==0.1.28
    azure-cli-backup==1.0.6
    azure-cli-batch==3.1.10
    azure-cli-batchai==0.1.5
    azure-cli-billing==0.1.7
    azure-cli-cdn==0.0.13
    azure-cli-cloud==2.0.12
    azure-cli-cognitiveservices==0.1.11
    azure-cli-command-modules-nspkg==2.0.1
    azure-cli-configure==2.0.14
    azure-cli-consumption==0.2.2
    azure-cli-container==0.1.19
    azure-cli-core==2.0.28
    azure-cli-cosmosdb==0.1.19
    azure-cli-dla==0.0.18
    azure-cli-dls==0.0.19
    azure-cli-eventgrid==0.1.10
    azure-cli-extension==0.0.9
    azure-cli-feedback==2.1.0
    azure-cli-find==0.2.8
    azure-cli-interactive==0.3.16
    azure-cli-iot==0.1.17
    azure-cli-keyvault==2.0.19
    azure-cli-lab==0.0.17
    azure-cli-monitor==0.1.2
    azure-cli-network==2.0.24
    azure-cli-nspkg==3.0.1
    azure-cli-profile==2.0.19
    azure-cli-rdbms==0.0.12
    azure-cli-redis==0.2.11
    azure-cli-reservations==0.1.1
    azure-cli-resource==2.0.24
    azure-cli-role==2.0.20
    azure-cli-servicefabric==0.0.10
    azure-cli-sql==2.0.22
    azure-cli-storage==2.0.26
    azure-cli-vm==2.0.27
    azure-common==1.1.8
    azure-datalake-store==0.0.17
    azure-graphrbac==0.31.0
    azure-keyvault==0.3.7
    azure-mgmt-advisor==0.1.0
    azure-mgmt-authorization==0.30.0
    azure-mgmt-batch==4.1.0
    azure-mgmt-batchai==0.2.0
    azure-mgmt-billing==0.1.0
    azure-mgmt-cdn==1.0.0
    azure-mgmt-cognitiveservices==1.0.0
    azure-mgmt-compute==3.1.0rc3
    azure-mgmt-consumption==2.0.0
    azure-mgmt-containerinstance==0.3.1
    azure-mgmt-containerregistry==1.0.1
    azure-mgmt-containerservice==3.0.1
    azure-mgmt-cosmosdb==0.3.1
    azure-mgmt-datalake-analytics==0.2.0
    azure-mgmt-datalake-nspkg==2.0.0
    azure-mgmt-datalake-store==0.2.0
    azure-mgmt-devtestlabs==2.0.0
    azure-mgmt-dns==1.2.0
    azure-mgmt-eventgrid==0.4.0
    azure-mgmt-iothub==0.4.0
    azure-mgmt-iothubprovisioningservices==0.1.0
    azure-mgmt-keyvault==0.40.0
    azure-mgmt-marketplaceordering==0.1.0
    azure-mgmt-monitor==0.4.0
    azure-mgmt-msi==0.1.0
    azure-mgmt-network==1.7.0
    azure-mgmt-nspkg==2.0.0
    azure-mgmt-rdbms==0.1.0
    azure-mgmt-recoveryservices==0.1.0
    azure-mgmt-recoveryservicesbackup==0.1.1
    azure-mgmt-redis==4.1.0
    azure-mgmt-reservations==0.1.0
    azure-mgmt-resource==1.2.1
    azure-mgmt-servicefabric==0.1.0
    azure-mgmt-sql==0.8.5
    azure-mgmt-storage==1.5.0
    azure-mgmt-trafficmanager==0.40.0
    azure-mgmt-web==0.35.0
    azure-multiapi-storage==0.2.0
    azure-nspkg==2.0.0
    azure-storage==0.34.3
    azureml==0.2.7
    Babel==2.5.0
    backports.functools-lru-cache==1.4
    backports.shutil-get-terminal-size==1.0.0
    bcrypt==3.1.4
    beautifier==0.3.3
    beautifulsoup4==4.6.0
    bitarray==0.8.1
    bkcharts==0.2
    blaze==0.11.3
    bleach==2.0.0
    bokeh==0.12.7
    boto==2.48.0
    boto3==1.4.8
    botocore==1.8.50
    Bottleneck==1.2.1
    bqplot==0.10.5
    brewer2mpl==1.4.1
    CacheControl==0.12.3
    certifi==2017.7.27.1
    cffi==1.10.0
    chardet==3.0.4
    click==6.7
    cloudpickle==0.4.0
    clyent==1.2.2
    cntk==2.0
    colorama==0.3.9
    conda==4.4.11
    conda-build==3.0.27
    conda-verify==2.0.0
    constantly==15.1.0
    contextlib2==0.5.5
    cryptography==2.0.3
    cycler==0.10.0
    Cython==0.26.1
    cytoolz==0.8.2
    dask==0.15.3
    datacleaner==0.1.5
    datashape==0.5.4
    decorator==4.1.2
    distributed==1.19.1
    docker-py==1.10.6
    docker-pycreds==0.2.2
    docutils==0.14
    Dora==0.0.2
    edward==1.3.5
    elasticsearch==6.1.1
    entrypoints==0.2.3
    et-xmlfile==1.0.1
    fastcache==1.0.2
    feedparser==5.2.1
    filelock==2.0.12
    Flask==0.12.2
    Flask-Cors==3.0.3
    ftfy==5.3.0
    future==0.16.0
    GDAL==2.2.2
    gevent==1.2.2
    ggplot==0.11.5
    glob2==0.5
    gmpy2==2.0.8
    graphviz==0.8.2
    greenlet==0.4.12
    grpcio==1.10.0
    gym==0.10.3
    h5py==2.7.1
    heapdict==1.0.0
    holoviews==1.8.3
    html5lib==0.999999999
    humanfriendly==4.8
    hyperlink==18.0.0
    idna==2.6
    imageio==2.2.0
    imagesize==0.7.1
    incremental==17.5.0
    ipaddress==1.0.19
    ipykernel==4.7.0
    ipython==6.2.1
    ipython-genutils==0.2.0
    ipywidgets==7.0.0b7
    isodate==0.6.0
    isort==4.2.15
    itsdangerous==0.24
    jdcal==1.3
    jedi==0.10.2
    Jinja2==2.9.6
    jmespath==0.9.3
    joblib==0.11
    jsonschema==2.6.0
    jupyter-client==5.1.0
    jupyter-console==5.2.0
    jupyter-core==4.4.0
    jupyterlab==0.27.0
    jupyterlab-launcher==0.4.0
    kafka-python==1.4.1
    kazoo==2.4.0
    Keras==2.1.4
    keyring==11.0.0
    klein==17.10.0
    knack==0.3.1
    lazy-object-proxy==1.3.1
    line-profiler==2.1.2
    llvmlite==0.20.0
    locket==0.2.0
    lockfile==0.12.2
    luigi==2.7.2
    lxml==4.1.0
    MarkupSafe==1.0
    matplotlib==2.1.1
    mccabe==0.6.1
    memory-profiler==0.52.0
    mistune==0.7.4
    mock==2.0.0
    mpmath==0.19
    msgpack-python==0.4.8
    msrest==0.4.27
    msrestazure==0.4.22
    multipledispatch==0.4.9
    natsort==5.1.0
    navigator-updater==0.1.0
    nbconvert==5.3.1
    nbformat==4.4.0
    networkx==1.11
    nltk==3.2.4
    nose==1.3.7
    notebook==5.0.0
    numba==0.35.0
    numexpr==2.6.2
    numpy==1.12.1
    numpydoc==0.7.0
    oauthlib==2.0.6
    odo==0.5.1
    olefile==0.44
    openfst==1.6.1
    openpyxl==2.4.8
    packaging==16.8
    pandas==0.20.3
    pandasql==0.7.3
    pandocfilters==1.4.2
    param==1.5.1
    paramiko==2.4.0
    partd==0.3.8
    path.py==10.3.1
    pathlib2==2.3.0
    patsy==0.4.1
    pbr==3.1.1
    pep8==1.7.0
    pexpect==4.2.1
    phonenumbers==8.9.1
    pickleshare==0.7.4
    Pillow==4.3.0
    pkginfo==1.4.1
    plotly==2.4.1
    ply==3.10
    prettypandas==0.0.3
    prompt-toolkit==1.0.15
    protobuf==3.4.1
    psutil==5.4.0
    psycopg2==2.7.1
    ptyprocess==0.5.2
    py==1.4.34
    pyang==1.7.4
    pyasn1==0.4.2
    pyasn1-modules==0.2.1
    pycodestyle==2.3.1
    pycosat==0.6.3
    pycparser==2.18
    pycrypto==2.6.1
    pycurl==7.43.0
    pydocumentdb==2.3.1
    pydot==1.2.4
    pyflakes==1.6.0
    PyGithub==1.37
    pyglet==1.3.1
    Pygments==2.2.0
    PyJWT==1.6.0
    pykafka==2.7.0
    pylint==1.7.4
    pymc==2.3.6
    pymc3==3.3
    pymongo==3.4.0
    Pympler==0.5
    pymssql==2.1.1
    PyMySQL==0.7.9
    PyNaCl==1.2.1
    pyodbc==3.1.1
    pyOpenSSL==17.2.0
    pypachy==0.1.5
    pyparsing==2.2.0
    pyprof2calltree==1.4.3
    PySocks==1.6.7
    pytest==3.2.1
    python-daemon==2.1.2
    python-dateutil==2.6.1
    pytz==2017.2
    PyWavelets==0.5.2
    PyYAML==3.12
    pyzmq==16.0.2
    QtAwesome==0.4.4
    qtconsole==4.3.1
    QtPy==1.3.1
    readline==6.2.4.1
    requests==2.18.4
    requests-oauthlib==0.8.0
    rope==0.10.5
    rpy2==2.9.2
    ruamel-yaml==0.11.14
    s3transfer==0.1.13
    scikit-bio==0.5.1
    scikit-image==0.13.0
    scikit-learn==0.19.1
    scipy==0.19.1
    scp==0.10.2
    scrubadub==1.2.0
    seaborn==0.8
    SecretStorage==2.3.1
    service-identity==17.0.0
    simplegeneric==0.8.1
    singledispatch==3.4.0.3
    six==1.11.0
    sklearn==0.0
    snakeviz==0.4.2
    snowballstemmer==1.2.1
    sortedcollections==0.5.3
    sortedcontainers==1.5.7
    Sphinx==1.6.3
    sphinxcontrib-websupport==1.0.1
    spyder==3.2.4
    SQLAlchemy==1.1.13
    sshtunnel==0.1.3
    statsmodels==0.8.0
    sympy==1.1.1
    tables==3.4.2
    tabulate==0.7.7
    tblib==1.3.2
    tensorflow==1.1.0
    terminado==0.6
    testpath==0.3.1
    textblob==0.10.0
    Theano==1.0.1
    toolz==0.8.2
    torch==0.3.1.post2
    torchvision==0.1.9
    tornado==4.5.2
    tqdm==4.19.6
    traitlets==4.3.2
    traittypes==0.0.6
    treq==17.8.0
    Twisted==17.9.0
    typing==3.6.2
    unicodecsv==0.14.1
    update-checker==0.16
    urllib3==1.22
    vega==0.4.4
    vsts-cd-manager==1.0.1
    wcwidth==0.1.7
    webencodings==0.5.1
    websocket-client==0.47.0
    Werkzeug==0.12.2
    Whoosh==2.7.4
    widgetsnbextension==3.0.0
    word2vec==0.9.2
    wrapt==1.10.11
    xlrd==1.1.0
    XlsxWriter==1.0.2
    xlwt==1.3.0
    xmltodict==0.11.0
    zict==0.1.3
    zope.interface==4.4.3
    [33mYou are using pip version 9.0.1, however version 9.0.3 is available.
    You should consider upgrading via the 'pip install --upgrade pip' command.[0m