matplotlib tips

Pythonのmatplotlibの自分用の備忘録 (2017/03/08作成) をQiitaに持ってきました。一応アップする前に見直しましたが、情報が古いままの箇所もあると思いますので注意して下さい。また、全機能を網羅するつもりはなく、個人的にハマった箇所を中心に書いただけです。

# 参考ページ

* [matplotlib入門](http://bicycle1885.hatenablog.com/entry/2014/02/14/023734)

* [matplotlib gallery](http://matplotlib.org/gallery.html)

# 基本の流れ

* 必要なパッケージを入れる
  ```
  import matplotlib.pyplot as plt
  import seaborn as sns
  ```
* figとaxの生成 (複数の図をまとめて1つの図にする場合があるので、全体をfig、個々の図をaxと呼んで区別している)
  ```
  fig, ax = plt.subplots()
  ```
  上は以下と等価
  ```
  fig = plt.figure()
  ax  = fig.add_subplot()
  ```
* メインの図の描画
  ```
  ax.plot(...)
  ```
* 細かい調整
  ```
  ax.set_xlabel('x')
  ```
* 表示
  ```
  fig.show()
  ```
* ファイルへ出力
  ```
  fig.savefig('tmp.png')
  ```

# 図の種類

## matplotlibの図

* 折れ線グラフ
  ```
  ax.plot(...)
  ```
* 散布図
  ```
  ax.scatter(...)
  ```
* 棒グラフ
  ```
  ax.bar(...)
  ```
* 箱ひげ図
  ```
  ax.boxplot(...)
  ```
* ヒストグラム
  ```
  ax.hist(...)
  ```
* ヒートマップ
  ```
  ax.matshow(...)
  ```
  または
  ```
  ax.imshow(...)
  ```
* 円グラフ
  ```
  ax.pie(...)
  ```

## seabornの図

* 折れ線グラフ
  ```
  sns.pointplot(...)
  ```
* 散布図
  ```
  sns.scatterplot(...)
  ```
* 棒グラフ (連続変数の場合)
  ```
  sns.barplot(...)
  ```
* 棒グラフ (カテゴリカル変数の場合)
  ```
  sns.countplot(...)
  ```
* 箱ひげ図
  ```
  sns.boxplot(...)
  ```
* 蜂群図
  ```
  sns.swarmplot(...)
  ```
* バイオリン図
  ```
  sns.violinplot(...)
  ```
* ヒストグラムとカーネル密度推定 (古いバージョン)
  ```
  sns.distplot(...)
  ```
* ヒストグラムとカーネル密度推定 (新しいバージョン)
  ```
  sns.displot(...)
  sns.histplot(...)
  sns.kdeplot(...)
  ```
* ヒートマップ
  ```
  sns.heatmap(...)
  ```
* ヒートマップ (樹形図付き)
  ```
  sns.clustermap(...)
  ```

# 全体的なスタイル

* 全体的な文字サイズと線の太さを変える (paper < notebook (default) < talk < poster)
  ```
  sns.set_context('talk')
  ```
  文字サイズを調整したい場合
  ```
  sns.set_context('talk', font_scale=0.8)
  ```
  一時的に適用したい場合 (withの内側のみ)
  ```
  with sns.plotting_context('talk'):
    # 略
  ```
* rcParamsを個別に変更する
  ```
  with plt.rc_context({'lines.linewidth':5}):
  ```
  または
  ```
  with sns.plotting_context(..., rc={'lines.linewidth':5}):
  ```
* 描画スタイルを変える (darkgrid, whitegrid, dark, white, ticks)
  ```
  with sns.axes_style('whitegrid'):
  ```
* 使う色の種類を変える
  ```
  with sns.color_palette('colorblind'):
  ```

# タイトル

* タイトルを付ける
  ```
  ax.set_title('title')
  ```
* タイトルの文字を大きくする
  ```
  ax.set_title(..., fontsize=20)
  ```
* タイトルと図の間を広げる
  ```
  ax.set_title(..., pad=10)
  ```
* 複数のaxがある場合に全体タイトルを付ける
  ```
  fig.suptitle('title')
  ```
  tight_layoutを使う場合
  ```
  fig.tight_layout(rect=[0,0,1,0.95])
  ```

# figとax

* figのサイズを指定 (単位はインチ)
  ```
  fig, ax = plt.subplots(figsize=(8,6))
  ```
  pixelで指定したい場合 (dpiは予め適当なfig作ってfig.get_dpi()で調べておく)
  ```
  dpi = 80.0
  fig, ax = plt.subplots(figsize=(400/dpi, 300/dpi))
  # (中略)
  fig.savefig('tmp.png', dpi='figure')
  ```
* 余白を詰める
  ```
  fig.tight_layout()
  ```
  または
  ```
  fig.subplots_adjust(left=0.05, top=0.95, bottom=0.05, right=0.95)
  ```
* ax同士の間を調節する (hはheightで縦方向、wはwidthで横方向)
  ```
  fig.tight_layout(h_pad=0.1, w_pad=0.1)
  ```
  または
  ```
  fig.subplots_adjust(wspace=0.1, hspace=0.1)
  ```
* 複数のaxを並べる (サイズが同じ場合)
  ```
  fig, axes = plt.subplots(nrows=2, ncols=2)
  ax1 = axes[0,0]
  ```
* 複数のaxを並べる (サイズがばらばらの場合、方眼紙を作ってカスタムで配置するイメージ)
  ```
  fig = plt.figure()
  gs = fig.add_gridspec(100, 100)
  ax = fig.add_subplot(gs[:50, :50])
  ```
* 複数のaxを一次元配列にする
  ```
  axes = axes.flatten()
  ```
* カスタムサイズのaxを作る (左下を(0,0), 右上を(1,1)として、[left, bottom, width, height] の順)
  ```
  ax = fig.add_axes([0, 0, 1, 1])
  ```

# 描画範囲

* 描画範囲を指定
  ```
  ax.set_xlim((0, 100))
  ```
* 余白を入れる (データの範囲に対する割合。xlim, ylim指定があると無視される)
  ```
  ax.margins(0.05)
  ```

# 目盛り

* 目盛りの表示間隔や値を変更
  ```
  ax.set_xticks([1,3])
  ax.set_xticklabels([10,30])
  ```
  単に間引きたい場合
  ```
  xticks = ax.get_xticks()
  xlabels = ax.get_xticklabels()
  ax.set_xticks(xticks[::5])
  ax.set_xticklabels(xlabels[::5])
  ```
  sns.clustermapで全ての目盛りを表示したい場合
  ```
  sns.clustermap(df, yticklabels=1)
  ```
* 目盛りの文字の大きさを変える
  ```
  ax.tick_params(labelsize=20)
  ```
* 目盛りの文字の向きを変える (横書きは0, 縦書きは90, 斜めも可)
  ```
  ax.set_xticklabels(..., rotation=90)
  ```
* 目盛りのラベルを置換
  ```
  labels = [v.get_text().replace('-',' ') for v in ax.get_xticklabels()]
  ax.set_xticklabels(labels)
  ```
* 目盛りの位置を移動
  ```
  ax.xaxis.tick_bottom()
  ```
  上は以下と同じ
  ```
  ax.tick_params(top=False, bottom=True)
  ```
* 目盛り (軸) を0を横切る位置に移動
  ```
  ax.spines['left'].set_position('zero')
  ```
* 軸の先を矢印にする (x_max, y_maxはそれぞれx軸、y軸の最大値)
  ```
  ax.plot(x_max, 0, marker='>', ms=10, color='k', clip_on=False)
  ax.plot(0, y_max, marker='^', ms=10, color='k', clip_on=False)
  ```
* 目盛りを消す
  ```
  ax.set_xticks([])
  ```
* 目盛りと辺を全部消す
  ```
  ax.axis('off')
  ```
* 目盛りの太さを変える
  ```
  ax.tick_params(width=3)
  ```
* 目盛りの長さを変える
  ```
  ax.tick_params(length=5)
  ```
* 目盛りを外向きにする
  ```
  ax.tick_params(direction='out')
  ```
* 目盛りと文字の間を広げる
  ```
  ax.tick_params(pad=10)
  ```
* 目盛りの表記フォーマットを変更
  ```
  def fmt(x, pos):
    return '{:.2e}'.format(x)
  ax.xaxis.set_major_formatter(plt.FuncFormatter(fmt))
  ```
  日付の場合
  ```
  import matplotlib.dates as mdates
  ax.xaxis.set_major_formatter(mdates.DateFormatter('%b\n%Y'))
  ```

# 軸の名前

* 軸の名前を付ける
  ```
  ax.set_xlabel('x')
  ```
* 軸の名前の文字の大きさを変える
  ```
  ax.set_xlabel(..., fontsize=20)
  ```
* 軸の名前の位置を移動
  ```
  ax.yaxis.set_label_position('right')
  ```

# 辺 (枠線)

* 辺を消す
  ```
  ax.spines['top'].set_visible(False)
  ```
  または、以下の方法 (初期設定で上と右を消す、左と下はオプションでTrue指定) 
  ```
  sns.despine()
  ```
* 辺と目盛りを全部消す (再掲)
  ```
  ax.axis('off')
  ```
* 辺を離して交わらないようにする
  ```
  ax.spines['bottom'].set_position(('outward', 10))
  ```
  または
  ```
  sns.despine(offset=10, trim=True)
  ```
* sns.heatmapに枠線を付ける
  ```
  for spine in ax.spines.values():
    spine.set_visible(True)
    spine.set_linewidth(2)
  ```

# 凡例

* 凡例を表示する (予め各プロットでlabelを指定)
  ```
  ax.plot(..., label='a')
  ax.plot(..., label='b')
  ax.legend()
  ```
* 凡例の位置を変える (upper/center/lowerとleft/center/rightの組み合わせ)
  ```
  ax.legend(loc='upper left')
  ```
* 凡例を図の外に移動する
  ```
  ax.legend(bbox_to_anchor=(1,1), loc='upper left')
  ```
* 凡例の枠の太さを調整
  ```
  leg = ax.legend(...)
  leg.get_frame().set_linewidth(3)
  ```
* 凡例の枠を消す
  ```
  ax.legend(frameon=False)
  ```
* 凡例のマーカーの大きさを調整
  ```
  ax.legend(markerscale=10)
  ```
* 凡例の周囲の余白を調整
  ```
  ax.legend(borderaxespad=0)
  ```
* 凡例のマーカーと文字の間隔を調整
  ```
  ax.legend(handletextpad=0)
  ```
* 凡例の行間を調整
  ```
  ax.legend(labelspacing=0)
  ```
* 凡例を消す (seabornで勝手に出力されてしまう場合など)
  ```
  ax.legend_.remove()
  ```
* 凡例を逆順にする
  ```
  handles, labels = ax.get_legend_handles_labels()
  leg = ax.legend(handles[::-1], labels[::-1])
  ```

# 文字や線を追加で書き込む

* 横腺を引く / 縦線を引く
  ```
  ax.axhline(y=0)
  ax.axvline(x=0)
  ```
* 部分領域を塗りつぶす
  ```
  ax.axvspan(0, 0.5, alpha=0.5)
  ```
* 文字を書く (x座標、y座標、文字)
  ```
  ax.text(0, 0, 'text', ha='center', va='center')
  ```
* 指定座標を左端にするか、中央にするかなどを変える (horizontal alignment, vertical alignment)
  ```
  ax.text(..., ha='left', va='center')
  ```
* 文字の位置を、座標軸の値でなく[0,1]の範囲で指定する
  ```
  ax.text(..., transform=ax.transAxes)
  ```
* 文字に背景色を付ける
  ```
  t = ax.text(...)
  t.set_bbox(dict(facecolor='white', alpha=0.5))
  ```
* 文字に縁取りを付ける
  ```
  from matplotlib import patheffects
  ax.text(..., path_effects=[patheffects.withStroke(linewidth=3, foreground='w')])
  ```
* 角が丸い四角形を描く
  ```
  from matplotlib import patches
  box = patches.FancyBboxPatch(...)
  ax.add_patch(box)
  ```
* patchesオブジェクトを回転させる (楕円ならオプションで指定可能)
  ```
  t = mpl.transforms.Affine2D().rotate_deg_around(x, y, theta) + ax.transData
  box.set_transform(t)
  ```

# 箱ひげ図

* 外れ値を非表示にする
  ```
  ax.boxplot(..., showfliers=False)
  ```
* ひげの先の横棒を消す
  ```
  ax.boxplot(..., showcaps=False)
  ```
* 中央の線の色を変える
  ```
  ax.boxplot(..., medianprops={'color':'white'})
  ```  
* 箱の色を変える (colorだと枠線の色も変わるので注意)
  ```
  ax.boxplot(..., boxprops={'fc':plt.cm.Paired(0)})
  ```

# ヒートマップ

* 基本
  ```
  ax.matshow(np.random.randn(200,100), aspect='auto')
  ```
  上は以下と等価 (matshowはimshowをorigin='upper', interpolation='nearest', aspect='equal'で呼ぶwrapper関数)
  ```
  ax.imshow(np.random.randn(200,100), aspect='auto', interpolaiton='nearest')
  ```
* カラーバーを別のaxに表示
  ```
  im = ax.matshow(...)
  cb = fig.colorbar(im, cax=ax2)
  ```
* カラーバーの枠の太さを変える
  ```
  cb.outline.set_linewidth(2)
  ```
* カラーバーの枠を消す
  ```
  cb.outline.set_visible(False)
  ```
* sns.heatmapのカラーバーの調節
  ```
  sns.heatmap(..., ax=ax)
  cax = ax.collections[0].colorbar.ax
  cax.tick_params(length=0)
  ```
* sns.heatmapでNoneの色を変える
  ```
  g = sns.heatmap(df, mask=df.isnull())
  g.set_facecolor('0.8')
  ```
* y軸を逆向きにする
  ```
  ax.invert_yaxis()
  ```

# sns.clustermap

* 基本 (予め用意したaxに入れるのでなく、これ自体がfigとaxを作るので注意)
  ```
  g = sns.clustermap(np.random.randn(200,100))
  ```
* figを取得
  ```
  fig = g.fig
  ```
* ヒートマップの部分のaxを取得
  ```
  ax = g.ax_heatmap
  ```
* 樹形図の占める割合を変更
  ```
  sns.clustermap(..., dendrogram_ratio=(0.1,0.1))
  ```