ipywidgetsでグラフをインタラクティブにする

# はじめに
データ分析作業をしていると、Jupyterでグラフを作成することがあります。分析の軸やパラメータが複数あると、パラメータごとにグラフを出力して流し見したいときがあります。しかし、データの探索段階ではしっかりグラフを作り込んだりするわけではないので、何度もパラメータを変えてグラフを出力してみる、ということをやります。Jupyterであっても何度もパラメータを変えて実行するのは手間です。

そんなときipywidgetsを使用して、グラフをインタラクティブに表示すると便利です。

# 参考
- [ipywidgets](https://ipywidgets.readthedocs.io/en/stable/index.html#)

# 環境
- Python 3.6.5 :: Anaconda, Inc.
- anaconda==5.2.0
- jupyter==1.0.0
- ipywidgets==7.2.1

# 手順
ipywidgetsはJupyterインストール時に一緒に入っていた？ようです。
jupyterとipywidgetsはインストールされている前提とします。

## 基本的な使い方
`%matplotlib inline`を記載しておきます。`interact`をインポートします。
グラフの表示部を関数化しておき、`interact`の第一引数に関数を、以降の引数に関数の引数を渡します。
この「関数の引数」に、一つの値ではなく値の範囲を渡します。するとその範囲のスライダーが生成され、インタラクティブなグラフになります。

下記の例では、`k=(0.0,5.0,0.1)`の部分で、0から5まで0.1刻みのFloatSliderを作成します。

```python
%matplotlib inline
import matplotlib.pyplot as plt
from ipywidgets import interact
import numpy as np

def f(k):
    x = np.linspace(0, 10, num=1000)
    y = np.sin(k*x)
    plt.plot(x, y)
    plt.show()

interact(f, k=(0.0,5.0,0.1) )
```
<img width="397" alt="スクリーンショット 2019-01-19 19.32.35.png" src="https://qiita-image-store.s3.amazonaws.com/0/211712/e25cc005-b56a-0f43-f4d1-d5b942314618.png">

上の例では関数の中でデータを生成していますが、下記の例では予め作成した複数のデータをスライダーで切り替えて表示しています。
`k=(0,param-1)`の第三引数を省略すると整数単位のIntSliderになります。

```python
%matplotlib inline
import matplotlib.pyplot as plt
from ipywidgets import interact
import numpy as np

x = np.linspace(0, 2*np.pi, num=5000)
y = []
param = 50

for i in range(1,param+1):
    y.append([np.sin(i*j) for j in x])

def f(k):
    plt.plot(x, y[k])
    plt.show()

interact(f, k=(0,param-1) )
```
<img width="397" alt="スクリーンショット 2019-01-19 19.37.28.png" src="https://qiita-image-store.s3.amazonaws.com/0/211712/7cc52d9f-10fb-78b0-ab8e-a60c1c908e3f.png">

## デコレータを使用した表示
グラフを表示する関数を`interact`でデコレーションするだけでもグラフを表示できます。
Jupyterでは関数を呼び出さず、下記そのままを実行するとグラフが表示されます。

```python
%matplotlib inline
import matplotlib.pyplot as plt
from ipywidgets import interact
import numpy as np

x = np.linspace(0, 2*np.pi, num=5000)
y = []
param = 50

for i in range(1,param+1):
    y.append([np.sin(i*j) for j in x])

@interact(k=(0,param-1))
def f(k):
    plt.plot(x, y[k])
    plt.show()
```

## グラフを複数表示
単純に二回記載すればグラフを２個表示できます。

```python
%matplotlib inline
import matplotlib.pyplot as plt
from ipywidgets import interact
import numpy as np

x = np.linspace(0, 2*np.pi, num=5000)
y1, y2 = [], []

param = 50

for i in range(1,param+1):
    y1.append([np.sin(i*j) for j in x])
    y2.append([np.cos(i*j)+np.cos(j) for j in x])

@interact(k=(0,param-1))
def f1(k):
    plt.plot(x, y1[k])
    plt.show()
    
@interact(k=(0,param-1))
def f2(k):
    plt.plot(x, y2[k])
    plt.show()
```
<img width="397" alt="スクリーンショット 2019-01-19 19.43.40.png" src="https://qiita-image-store.s3.amazonaws.com/0/211712/b642d663-6045-ea59-8049-7ea8ca1151c1.png">


## 選択肢の表示
関数の引数が数値でない場合はDropdown形式になります。

```python
%matplotlib inline
import matplotlib.pyplot as plt
from ipywidgets import interact
import numpy as np

x = np.linspace(0, 2*np.pi, num=5000)
y1, y2 = [], []

param = 50

for i in range(1,param+1):
    y1.append([np.sin(i*j) for j in x])
    y2.append([np.cos(i*j)+np.cos(j) for j in x])
    
@interact(k=(0,param-1), mode=['A','B'])
def f(k,mode):
    if mode=='A':
        plt.plot(x, y1[k])   
    elif mode=='B':        
        plt.plot(x, y2[k])
    else:
        print('Error')

    plt.show()
```
<img width="397" alt="スクリーンショット 2019-01-19 19.49.13.png" src="https://qiita-image-store.s3.amazonaws.com/0/211712/0615bfba-74c5-0940-59df-86d0ffbe09a7.png">

## subplotを用いた表示
subplotでも使用できます。

```python
%matplotlib inline
import matplotlib.pyplot as plt
from ipywidgets import interact
import numpy as np

x = np.linspace(0, 2*np.pi, num=5000)
y, y1, y2, y3 = [], [], [], []

param = 50

for i in range(1,param+1):
    y1.append([np.sin(i*j) for j in x])
    y2.append([np.cos(i*j)+np.cos(j) for j in x])
    y3.append([np.tan(i*j)+np.cos(j) for j in x])

y = [y1, y2, y3]

# グラフを作る
@interact(k=(0,param-1))
def f(k):
    fig = plt.figure(figsize=(17,4))

    # タイトルを配列に格納
    title=['title1','title2','title3']    
    
    # グラフ間の幅を調整
    plt.subplots_adjust(wspace=0.25, hspace=0.6)
    
    for i in range(3):
        ax = fig.add_subplot(1, 3, i+1)
        ax.plot(x, y[i][k])
        ax.set_xlabel('x-axis label') # x軸のラベル
        ax.set_ylabel('y-axis label') # y軸のラベル
        ax.set_title(title[i]) # タイトル

    plt.show()
```
<img width="981" alt="スクリーンショット 2019-01-19 20.33.19.png" src="https://qiita-image-store.s3.amazonaws.com/0/211712/f4d83db7-9887-935e-aa53-c45acffd0009.png">

次のように書いても表示できます。

```python
%matplotlib inline
import matplotlib.pyplot as plt
from ipywidgets import interact
import numpy as np

x = np.linspace(0, 2*np.pi, num=5000)
y, y1, y2, y3 = [], [], [], []

param = 50

for i in range(1,param+1):
    y1.append([np.sin(i*j) for j in x])
    y2.append([np.cos(i*j)+np.cos(j) for j in x])
    y3.append([np.tan(i*j)+np.cos(j) for j in x])

y = [y1, y2, y3]

# グラフを作る
@interact(k=(0,param-1))
def f(k):
    # 3つのグラフを配置する枠をつくる 
    fig,ax = plt.subplots(1,3,figsize=(17,4))
    
    # タイトルを配列に格納
    title=['title1','title2','title3']
    
    # グラフ間の幅を調整
    plt.subplots_adjust(wspace=0.25, hspace=0.6)
    
    for i in range(3):
        ax[i].plot(x, y[i][k])
        ax[i].set_title(title[i]) # タイトル
        ax[i].set_xlabel('x-axis label') # x軸のラベル
        ax[i].set_ylabel('y-axis label') # y軸のラベル
        
    plt.show()
```
<img width="981" alt="スクリーンショット 2019-01-19 20.40.55.png" src="https://qiita-image-store.s3.amazonaws.com/0/211712/a3280dab-c6f2-e569-9ee3-01a738d639dc.png">


縦バージョン。

```python
%matplotlib inline
import matplotlib.pyplot as plt
from ipywidgets import interact
import numpy as np

x = np.linspace(0, 2*np.pi, num=5000)
y, y1, y2, y3 = [], [], [], []

param = 50

for i in range(1,param+1):
    y1.append([np.sin(i*j) for j in x])
    y2.append([np.cos(i*j)+np.cos(j) for j in x])
    y3.append([np.tan(i*j)+np.cos(j) for j in x])

y = [y1, y2, y3]

# グラフを作る
@interact(k=(0,param-1))
def f(k):
    fig = plt.figure(figsize=(10,10))

    # タイトルを配列に格納
    title=['title1','title2','title3']    
    
    # グラフ間の幅を調整
    plt.subplots_adjust(wspace=0.2, hspace=0.6)
    
    for i in range(3):
        ax = fig.add_subplot(3, 1, i+1)
        ax.plot(x, y[i][k])
        ax.set_xlabel('x-axis label') # x軸のラベル
        ax.set_ylabel('y-axis label') # y軸のラベル
        ax.set_title(title[i]) # タイトル

    plt.show()
```
<img width="640" alt="スクリーンショット 2019-01-19 20.47.26.png" src="https://qiita-image-store.s3.amazonaws.com/0/211712/cb8d371b-5fb2-ed99-0094-9e258fdace27.png">

# おわりに
ipywidgetsを使用して、グラフをインタラクティブに表示できました。

ipywidgetsは他にもプログレスバーやチェックボックス、ラジオボタンなど様々なwidgetが用意されています。notebookをインタラクティブ化して効率的な可視化を行っていきましょう！