【Python】縦軸が左右交互に入れ替わる折れ線グラフ

タイトルだと分かりにくいので例：
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270660/5061022f-9fd0-2278-030f-e0bf35dbef4d.png)

基本的なアイデア：
- axesの位置を適宜計算，軸を互い違いにする利点を生かすため若干重複させる（overlap_ratio）
- 不要な軸は消去，背景も透明に（そのままだと重なって表示される）

``` Python:axis_left_right.py
import numpy as np
import matplotlib.pyplot as plt

def calc_axes_size(ax_num=1, overlap_ratio=0.0, height_all=0.8):
    return height_all / (ax_num - (ax_num - 1) * overlap_ratio)


def plot_alternately(fig, ys, colors=None, options=None,
                     overlap_ratio=0.0,
                     left_all=0.125, bottom_all=0.1, width_all=0.775, height_all=0.8):
    n = len(ys)
    ax_size = calc_axes_size(n, overlap_ratio, height_all)
    if colors is None:
        colors = ['k'] * n
    if options is None:
        options = [{}] * n

    for i, (y, color, option) in enumerate(zip(ys, colors, options)):
        bottom = (1.0 - overlap_ratio) * ax_size * (n - i - 1) + bottom_all
        ax = plt.axes((left_all, bottom, width_all, ax_size))
        ax.set_facecolor((1.0, 1.0, 1.0, 0.0)) # (R,G,B,alpha)

        left, right = True, False
        if i % 2 == 1: # right axis, ax.twinx() does not work
            left, right = False, True

        top = False
        if i == 0:
            top = True

        bottom = False
        if i + 1 == n:
            bottom = True

        # delete axis
        ax.tick_params(labelbottom=bottom, labelleft=left, labelright=right, labeltop=top,
                       bottom     =bottom, left     =left, right     =right, top     =top)
        ax.spines['left'  ].set_visible(left)
        ax.spines['right' ].set_visible(right)
        ax.spines['top'   ].set_visible(top)
        ax.spines['bottom'].set_visible(bottom)

        # change color
        ax.spines['left'  ].set_color(color)
        ax.spines['right' ].set_color(color)
        ax.tick_params(axis='y', colors=color)

        ax.plot(y, color=color, **option)
    plt.show()
```

上図を生成したコード

``` Python
def make_test_data(x, seed=0):
    '''
    y = sin(x) + ax + b, 0 < a, b < 1
    '''
    np.random.seed(seed=seed)
    return np.sin(x) + 0.05 * np.random.rand() * x + np.random.rand(*x.shape)


def main(*args):
    plot_length = 100
    plot_number = 4
    x  = np.arange(plot_length)
    ys = [make_test_data(x, i) for i in range(plot_number)]

    cs   = ['r', 'b', 'y', 'g']
    lss  = ['solid', 'dashed', 'dashdot', 'dotted']
    opts = [{'linestyle':ls} for ls in lss]

    fig = plt.figure(figsize=(6, 4))
    plot_alternately(fig, ys, cs, opts, 0.25)
    return
```

axesの位置の計算を外に出すと，より柔軟に図の操作が可能になる．
例えば，2本の折れ線グラフを1組と見て，組内の重なりと組間の重なりを変える：

``` Python

def calc_axes_size1(ax_num=1, overlap_ratio=0.0, height_all=0.8):
    return height_all / (ax_num - (ax_num - 1) * overlap_ratio)


def calc_axes_bottoms1(ax_num=2, overlap_ratio=0.0, height_all=0.8):
    ax_size = calc_axes_size(ax_num, overlap_ratio, height_all)
    return [(1.0 - overlap_ratio) * ax_size * (ax_num - i - 1) for i in range(ax_num)]


def calc_axes_size2(ax_num=2, overlap_ratio1=0.0, overlap_ratio2=0.0, height_all=0.8):
    if ax_num % 2 == 1:
        print 'calc_axes_positions ERROR: ax_num =', ax_num
        sys.exit()
    ol_num1 = ax_num / 2
    ol_num2 = (ax_num + 1) / 2 - 1
    return height_all / (ax_num - ol_num1 * overlap_ratio1 - ol_num2 * overlap_ratio2)


def calc_axes_bottoms2(ax_num=2, overlap_ratio1=0.0, overlap_ratio2=0.0, height_all=0.8):
    ax_size = calc_axes_size2(ax_num, overlap_ratio1, overlap_ratio2, height_all)

    bottoms = []
    for i in range(1, ax_num+1):
        ol_num1 = i / 2
        ol_num2 = (i + 1) / 2 - 1
        bottom = height_all - ax_size * (i - overlap_ratio1 * ol_num1 - overlap_ratio2 * ol_num2)
        bottoms.append(bottom)
    return bottoms

# ---------------------------------------------------------------------------------------------------
def plot_alternately(fig, ys, colors=None, options=None,
                     overlap_ratio=0.0,
                     left_all=0.125, bottom_all=0.1, width_all=0.775, height_all=0.8):
    n = len(ys)
    if len(overlap_ratio) == 1:
        ax_size = calc_axes_size1   (n, overlap_ratio[0], height_all)
        bottoms = calc_axes_bottoms1(n, overlap_ratio[0], height_all)
    elif len(overlap_ratio) == 2:
        ax_size = calc_axes_size2   (n, overlap_ratio[0], overlap_ratio[1], height_all)
        bottoms = calc_axes_bottoms2(n, overlap_ratio[0], overlap_ratio[1], height_all)

    if colors is None:
        colors = ['k'] * n
    if options is None:
        options = [{}] * n

    for i, (y, bottom, color, option) in enumerate(zip(ys, bottoms, colors, options)):
        ax = plt.axes((left_all, bottom + bottom_all, width_all, ax_size))
        # 後は同じ
```

![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270660/ce83bd41-26f9-455f-39d7-7dd0cd180bbe.png)

用途例：複数都市の気温と降水量（もっと正の相関がある変数の方がズラして描く意味がある？）
subplotで良い気もする．一般化は植木算を頑張る．