Jupyter notebookでHighcharts

# Highchartsとは
Javascriptで動作する可視化ライブラリです。  
下記のような機能/特徴があります。  

* ピュアJavaScript  
  プラグイン不要、2つのJSファイルだけで動作
* 多彩なチャートタイプ
  * 折れ線グラフ
  * スプライン曲線
  * 面グラフ
  * エリアスプライン
  * 横棒グラフ
  * 縦棒グラフ
  * 散布図
  * OHLC
  * ローソク足
  * エリアレンジ
  * エリアスプラインレンジ
* レンジセレクタ
  * 時系列データを素早く表示
  * 時間範囲の選択  
    1か月、1年、日付指定
  * 時間範囲のチャートをズーム
* ツールチップ・ラベル
  * データ系列やポイントに関する情報をツールチップ・テキストで表示
  * マウスをグラフの上に動かすと、ツールチップが表示
* エクスポートと印刷
  * ボタンをクリックして指定のフォーマットでエクスポート
    PNG、JPG、PDF、SVG
  * ウェブ・ページから直接チャートを印刷

非常に高機能ですが、商用は有償となります。  

# なぜHighcharts？

## きれい
[公式サイト](http://www.highcharts.com/)のサンプルを見ていただければわかるとおり、色々と工夫をしなくてもきれいでカッコイイグラフが描けます。

## かるい
爆速です。  
サンプルとして、下記のような1000個の点を7つのグループに色分けした散布図を描画してみます。

```python
import numpy as np
import pandas as pd

num = 1000
x, y = np.random.random((2, num))
labels = np.random.choice(['a', 'b', 'c', 'e', 'f', 'g', 'h'], num)
df = pd.DataFrame(dict(x=x, y=y, label=labels))
```

下記の代表的な3つのライブラリと比較してみます。
1. Matplotlib
2. Bokeh
3. Plotly

### Matplotlib
```python
import matplotlib.pyplot as plt
groups = df.groupby('label')
colors = 'bgrcmyk'
for i, (name, group) in enumerate(groups):
    plt.scatter(group.x, group.y, color=colors[i])
```

### Bokeh
```python
from bokeh.charts import Scatter, output_notebook, show
output_notebook()
show(Scatter(x='x', y='y', color='label', data=df))
```

### Plotly
```python
from plotly.offline import init_notebook_mode, iplot
init_notebook_mode()
groups = df.groupby('label')
data = [{'x': group.x, 'y': group.y, 'mode': 'markers', 'name': name} for name, group in groups]
fig = {'data': data}
iplot(fig)
```

### Highcharts
```python
from highcharts import Highchart

groups = df.groupby('label')
options = {
    'chart': {
        'type': 'scatter',
    },
}
H = Highchart()
H.set_dict_options(options)
for name, group in groups:
    data = list(zip(group.x, group.y))
    H.add_data_set(data, 'scatter', name)
H
```

結果はこんな感じになります。

### import(初期化)含む
![benchmark01.png](https://qiita-image-store.s3.amazonaws.com/0/22023/fdc0eb5a-ea25-8aa1-7b76-4123f4dd8461.png)

### import(初期化)含まない
![benchmark02.png](https://qiita-image-store.s3.amazonaws.com/0/22023/1912e607-f011-5432-7eb3-7c41f2b432d6.png)

理由は色々考えられますが、とにかく軽いですね。

# %%htmlマジックで実装

HighchartsはJavaScriptのライブラリなので、Jupyter notebookで使うには少し工夫をする必要があります。  
テンプレートエンジン等を使用する手段もありますが、単純な例として%%htmlマジックを使ってみます。

```python
from IPython.display import HTML
```
```python
%%html
    <script src="http://code.highcharts.com/highcharts.js"></script>
    <script src="http://code.highcharts.com/modules/exporting.js"></script>
    <div id="container" style="width:300px; height:200px;"></div>
    <script>
        plot = function () { 
            $('#container').highcharts({
                chart: {
                    type: 'line'
                },
                series: [{
                    data: [1, 2, 3]
                }]
            });
        };
        plot();
    </script>
```
![htmlmagic.png](https://qiita-image-store.s3.amazonaws.com/0/22023/c6fb7784-066e-ad28-d5b9-64960bdd39c4.png)

# python-highchartで実装
前述した%%htmlマジックでは、単にJavaScriptを書いているだけでPythonではありません。更に変数をハンドリングしたいときにテンプレートエンジン等を使う必要があり、色々と面倒です。

そこで、PythonからHighchartsを呼び出せるpython-highchartsを使ってみます。

## インストール
pipでインストールできます。  
```bash
pip install python-highcharts
```

## 線グラフ
前述の%%htmlマジックで実装した線グラフと同じものをpython-highchartsで実装すると下記のようになります。

```python
from highcharts import Highchart

H = Highchart(width=300, height=200)
H.add_data_set([1, 2, 3])
H
```

非常にすっきりして、しかもPythonで書けるようになりました。

## グラフのオプション
グラフの体裁等は`highcharts.Highchart.set_options()`に辞書型の値を渡します。  
設定可能な値は[Highchartsのリファレンス](http://api.highcharts.com/highcharts)を参照してください。  

```python
from highcharts import Highchart

H = Highchart(width=300, height=200)
options = {
    'title': {
        'text': 'メインタイトル'
    },
    'subtitle': {
        'text': 'サブタイトル'
    },
    'xAxis': {
        'title': {'text': 'X軸'}
    },
    'yAxis': {
        'title': {'text': 'Y軸'},
        'lineWidth': 2
    },
}
H.set_dict_options(options)
H.add_data_set([1, 2, 3])
H
```
![options.png](https://qiita-image-store.s3.amazonaws.com/0/22023/07feb6a7-f4d6-630b-9c17-e963c9b94843.png)

個別のパラメータ毎に設定することも可能です。  
こちらのほうが可読性が高いかもしれません。

```python
from highcharts import Highchart

H = Highchart(width=300, height=200)
H.set_options('title', {'text': 'メインタイトル'})
H.set_options('subtitle', {'text': 'サブタイトル'})
H.set_options('xAxis', {'title': {'text': 'X軸'}})
H.set_options('yAxis', {'title': {'text': 'Y軸'}, 'lineWidth': 2})
H.add_data_set([1, 2, 3])
H
```

要素の体裁は`highcharts.Highchart.add_data_set()`で設定します。  

```python
from highcharts import Highchart

H = Highchart(width=300, height=200)
H.add_data_set([1, 2, 3], dashStyle='ShortDash', color='plum', lineWidth=6)
H
```
![glyph.png](https://qiita-image-store.s3.amazonaws.com/0/22023/9efa2639-7f1a-9173-4595-7d68116a8a2b.png)

## グラフの種類

`highcharts.Highchart.add_data_set()`の第二引数にグラフの種類を指定します。各位置パラメータの指定は下記のとおりです。  

| 位置パラメータ | オブジェクト | 型           |
|:------------:|------------|-------------|
|1             |データセット  |リスト、タプル|
|2             |グラフの種類  |文字列       |
|3             |名前         |文字列       |

下記の例では1番目の要素を`data1`という名前の面グラフ、2番めの要素を`data2`という名前の棒グラフを出力しています。  

```python
from highcharts import Highchart

H = Highchart(width=300, height=200)
H.add_data_set([1, 2, 3], 'area', 'data1')
H.add_data_set([4, 5, 6], 'bar', 'data2')
H
```
![bar.png](https://qiita-image-store.s3.amazonaws.com/0/22023/8932d284-9571-4ed8-1076-bde5323faf3e.png)


## ドリルダウン
グラフの要素をクリックすると、更に別なグラフが表示される・・・なんてことが可能です。  

`highcharts.Highchart.add_data_set()`の各要素に辞書型のデータを渡します。辞書のキーに`drilldown`、値にドリルダウンに対応する名前を設定します。  

ドリルダウン後の要素の指定は`highcharts.Highchart.add_drilldown_data_set()`に前述した位置パラメータの3つの引数を指定します。3つ目の名前と上位の要素を対応させます。

```python
from highcharts import Highchart

H = Highchart(width=400, height=200)
data = [{
    'y': 1,
    'drilldown': 'a'
}, {
    'y': 2,
    'drilldown': 'b'
}, {
    'y': 3,
    'drilldown': 'c'
}]
H.add_data_set(data, 'column')
H.add_drilldown_data_set([0.3, 0.4, 0.3], 'pie', 'a')
H.add_drilldown_data_set([4, 5, 6], 'line', 'b')
H.add_drilldown_data_set([7, 8, 9], 'area', 'c')
H
```
![drilldown.gif](https://qiita-image-store.s3.amazonaws.com/0/22023/2ad9d376-46ba-1cc0-4c64-50d739f46450.gif)


## おまけ(やきうhack)
ドリルダウンを利用した例として、2016年のプロ野球12球団の勝利数を棒グラフに表示し、各要素をクリックすると、投手の勝利数が出力されるグラフを作成してみます。

```python
import pandas as pd
from highcharts import Highchart


class Team:
    def __init__(self):
        self.team_names = ['Hawks', 'Fighters', 'Marines', 'Lions',
                           'Buffaloes', 'Eagles', 'Swallows', 'Giants',
                           'Tigers', 'Carp', 'Dragons', 'BayStars']
        self.urls = [self.make_url(x) for x in self.team_names]
        self.dfs = [self.load_pitcher_win_data(url) for url in self.urls]
        self.wins = [df['win'].sum() for df in self.dfs]
        self.team_data = [
            self.make_y_dict(team_name, wins)
            for team_name, wins in zip(self.team_names, self.wins)
        ]
        self.pitcher_data = [df.values.tolist() for df in self.dfs]

    def make_url(self, team_name):
        def join_url(x):
            return ''.join(('http://npb.jp/bis/2016/stats/idp1_', x, '.html'))

        if team_name == 'Buffaloes':
            return join_url('bs')
        elif team_name == 'BayStars':
            return join_url('db')
        else:
            return join_url(team_name[0].lower())

    def load_pitcher_win_data(self, url):
        tables = pd.read_html(url)
        df = tables[0].iloc[2:, [1, 3]]
        df.columns = ['pitcher', 'win']
        df['win'] = df['win'].astype(float)
        return df[df['win'] > 0]

    def make_y_dict(self, team_name, wins):
        return {'name': team_name, 'y': wins, 'drilldown': team_name}

t = Team()

options = {
    'chart': {
        'type': 'column'
    },
    'title': {
        'text': '2016年勝利数'
    },
    'subtitle': {
        'text': 'Click the columns to view pitchers.'
    },
    'xAxis': {
        'type': 'category'
    },
    'yAxis': {
        'title': {
            'text': 'win'
        }
    },
    'legend': {
        'enabled': False
    },
    'plotOptions': {
        'series': {
            'borderWidth': 0,
            'dataLabels': {
                'enabled': True,
            }
        }
    },
    'tooltip': {
        'headerFormat':
        '<span style="font-size:11px">{series.name}</span><br>',
    },
}


H = Highchart(width=850, height=400)
H.set_dict_options(options)
H.add_data_set(t.team_data, 'column', "Team", colorByPoint=True)
for i, team_name in enumerate(t.team_names):
    H.add_drilldown_data_set(
        t.pitcher_data[i], 'column', team_name, name=team_name)
H
```
![baseball.gif](https://qiita-image-store.s3.amazonaws.com/0/22023/95802d04-f622-73ec-42e8-d2d82cffdd22.gif)

# pandas-highchartsで実装

## インストール
pipでインストールできます。
```bash
pip install pandas-highcharts
```

その名のとおり、pandasのDataFrameをHighchartsで描画します。  
なんと、`pandas_highcharts.display.display_charts`に`pandas.DataFrame.plot()`と同じ引数を渡すだけでグラフを出力することができます。  

```python
import pandas as pd
from pandas_highcharts.display import display_charts

df = pd.DataFrame([1, 2, 3], index=[list('abc')])
display_charts(df, figsize=(300, 200))
```
![pandas01.png](https://qiita-image-store.s3.amazonaws.com/0/22023/176e988f-d555-4f2c-a22d-97c556b85521.png)

グラフの種類の指定もpandasと同一の記述方法になります。

```python
import pandas as pd
from pandas_highcharts.display import display_charts

df = pd.DataFrame([1, 2, 3], index=[list('abc')])
display_charts(df, kind='bar', figsize=(300, 200))
```
![pandas02.png](https://qiita-image-store.s3.amazonaws.com/0/22023/ec33a4cc-e1d3-aa42-0b64-684a3e330b52.png)

機能面ではpython-highchartsの方が豊富ですが、pandasのデータを可視化したいならこちらのほうがお手軽ですね。