【自分メモ】Pythonのデータ可視化モジュール『Matplotlib』逆引きチートシート

前提条件

モジュールのインポート

import numpy as np
import pandas as pd
import matplotlib as mpl
import matplotlib.pyplot as plt

# 日本語フォントの設定
mpl.font_manager._rebuild()
plt.rcParams['font.family'] = 'IPAexGothic'

# Jupyter Notebookに描画する
%matplotlib inline

インターフェースの流儀

名称の確認

図(Figure)

fig, ax = plt.subplots()

# fig : <class 'matplotlib.figure.Figure'>
# ax  : <class 'matplotlib.axes._subplots.AxesSubplot'>

デザイン

図のアスペクト比を変更する場合、matplotlib.pyplotクラスのsubplotsメソッドに、figsize=(横, 縦)を指定します。

fig, ax = plt.subplots(figsize=(6, 6)) # default: figsize=(6, 4)

図の解像度を変更する場合、matplotlib.pyplotクラスのsubplotsメソッドに、dpi=数値を指定します。

fig, ax = plt.subplots(dpi=144) # default: dpi=72

図の背景色を変更する場合、matplotlib.pyplotクラスのsubplotsメソッドに、facecolor='カラーコード'を指定します。

fig, ax = plt.subplots(facecolor='#54565D') # default: facecolor='#FFFFFF'

Axesも塗りつぶしたい場合は、matplotlib.axes.Axesクラスのset_facecolorメソッドにカラーコードを指定します。

fig, ax = plt.subplots(facecolor='#54565D')

# Axesの背景色をセット
ax.set_facecolor('#54565D')

サブプロット

fig, ax = plt.subplots(
    nrows = 2,
    tight_layout = True # Axes同士が重ならないようにするため
)

fig, ax = plt.subplots(
    ncols = 2,
    tight_layout = True # Axes同士が重ならないようにするため
)

fig, ax = plt.subplots(
    nrows = 2,
    ncols = 2,
    tight_layout = True # Axes同士が重ならないようにするため
)

fig, ax = plt.subplots(
    nrows = 3,
    ncols = 2,
#   tight_layout = True, gridspec_kwのhspace、wspaceを指定する場合、不要
    gridspec_kw = dict(
        height_ratios = [2, 1, 3], # 縦のAxesの比率、2:1:3
        width_ratios = [1, 3],     # 横のAxesの比率、1:3
        hspace = 0.5,              # 縦のAxes間のスペース
        wspace = 0.3               # 横のAxes間のスペース
    )
)

gridspec_kwパラメータにhspaceまたはwspaceを指定する場合、tight_layout=Trueは不要です。好きな方を選びましょう。

from matplotlib.gridspec import GridSpec, GridSpecFromSubplotSpec

fig = plt.figure(figsize=(9, 5), tight_layout=True)

gs_master = GridSpec(
    nrows = 8,
    ncols = 12,
    height_ratios = np.ones(8, dtype=int),
    width_ratios = np.ones(12, dtype=int),
#     wspace=2, tight_layoutを
#     hspace=3  指定する場合、不要
)

ax1 = fig.add_subplot(GridSpecFromSubplotSpec(nrows=3, ncols=4, subplot_spec=gs_master[0:3, 0:4])[:, :])
ax2 = fig.add_subplot(GridSpecFromSubplotSpec(nrows=3, ncols=6, subplot_spec=gs_master[0:3, 4:10])[:, :])
ax3 = fig.add_subplot(GridSpecFromSubplotSpec(nrows=8, ncols=2, subplot_spec=gs_master[0:, 10:])[:, :])
ax4 = fig.add_subplot(GridSpecFromSubplotSpec(nrows=2, ncols=4, subplot_spec=gs_master[3:5, 0:4])[:, :])
ax5 = fig.add_subplot(GridSpecFromSubplotSpec(nrows=6, ncols=5, subplot_spec=gs_master[3:, 4:10])[:, :])
ax6 = fig.add_subplot(GridSpecFromSubplotSpec(nrows=3, ncols=2, subplot_spec=gs_master[5:, 0:2])[:, :])
ax7 = fig.add_subplot(GridSpecFromSubplotSpec(nrows=3, ncols=2, subplot_spec=gs_master[5:, 2:4])[:, :])

出力(画像として保存する)

Matplotlibで作成した図は、matplotlib.pyplotクラスのsavefigメソッドを使用して、画像として出力することができます。

MEMO
出力される画像はfigure単位です。1つのfigureの中に複数のAxesがある場合、まとめて出力されます。

matplotlibで作成した図は、pngやjpeg、pdf、svgなどのフォーマットで保存することができます。

plt.savefig(fname='{{ ファイル名のパス }}')

# ex. fname='sample.png'
# ex. fname='img/sample.svg'
fig, ax = plt.subplots()

# 図と座標軸の背景を透明にする
fig.patch.set_alpha(0)
ax.set_alpha(0)

plt.savefig(fname='{{ ファイル名のパス }}')

matplotlib.figure.Figureクラスに解像度(dpi)や図の背景色(facecolor)、図の枠線色(edgecolor)を変更している場合、以下のパラメータをmatplotlib.pyplotクラスのsavefigメソッドにそれぞれ指定する必要があります。

fig, ax = plt.subplots(
    dpi = 300,
    facecolor = '#54565D',
    edgecolor = '#000000'
)

plt.savefig(
    fname = '{{ ファイル名のパス }}',
    bbox_inches = 'tight',
    dpi = 300,
    facecolor = '#54565D',
    edgecolor = '#000000'
)

Jupyter Notebookのinline描画は、デフォルトでbbox_inches='tight'が適用されてます。

まったく同じ見た目で画像を保存したい場合、matplotlib.pyplotクラスのsavefigメソッドにbbox_inches='tight'を適用させます。

plt.savefig(fname='{{ ファイル名のパス }}', bbox_inches='tight')
参考 matplotlib.pyplot.savefig.htmlmatplotlib.org

座標軸(Axes)と軸(Axis)

枠線(Spines)

軸(Axis)

x軸の範囲
y軸の範囲
x軸を反転
y軸を反転

目盛りの幅

目盛りラベルの書式

目盛り/目盛りラベルの装飾

テキストと注釈

タイトル

凡例

座標軸にテキスト

指定領域の

補助線

図形

グラフ

時系列グラフ

下準備として、こんな感じのpandas.DataFrameを作成します。

df = pd.DataFrame(
    data = {
        'A': [464, 332, 447, 473, 376, 418, 328, 392, 327],
        'B': [409, 462, 540, 375, 412, 475, 425, 518, 526],
        'C': [306, 345, 368, 303, 485, 518, 504, 576, 401]
    },
    index = ['2019-07', '2019-08', '2019-09', '2019-10', '2019-11', '2019-12', '2020-01', '2020-02', '2020-03']
)
print(df)
#            A    B    C
# 2019-07  464  409  306
# 2019-08  332  462  345
# 2019-09  447  540  368
# 2019-10  473  375  303
# 2019-11  376  412  485
# 2019-12  418  475  518
# 2020-01  328  425  504
# 2020-02  392  518  576
# 2020-03  327  526  401

インデックスのデータの型がDataTimeになっていなければ、pandas.DataFrameto_datetimeメソッドを使用して、インデックスをDataTime型に変更させます。

df.index = pd.to_datetime(df.index)
print(df.index)
# DatetimeIndex(['2019-07-01', '2019-08-01', '2019-09-01', '2019-10-01',
#                '2019-11-01', '2019-12-01', '2020-01-01', '2020-02-01',
#                '2020-03-01'],
#               dtype='datetime64[ns]', freq=None)

fig, ax = plt.subplots()

x = df.index # x軸の値
y = df['A']  # y軸の値

# 時系列グラフをプロットする
ax.plot(x, y)

fig, ax = plt.subplots()

x = df.index

# 時系列グラフをまとめてプロットする
for _, item in enumerate(df.columns):
    ax.plot(x, df[item])

fig, ax = plt.subplots()

x = df.index

for _, item in enumerate(df.columns):
    ax.plot(x, df[item], linewidth=3)

fig, ax = plt.subplots()

x = df.index

for _, item in enumerate(df.columns):
    ax.plot(x, df[item], color='#BDBDBD')

from datetime import timedelta

fig, ax = plt.subplots()

x = df.index

for i, item in enumerate(df.columns):
    ax.plot(x, df[item])
    
    # グラフの右側にラベル(凡例)を表示する
    ax.text(
        x = x.max() + timedelta(days=3),
        y = df[item][-1],
        s = item,
        va = 'center'
    )

fig, ax = plt.subplots()

x = df.index



# カラム名で条件分岐して、カラーコードをセットする
color_dict = {i:'#6BCAB6' if i=='B' else '#BDBDBD' for i in df.columns}

# リスト内包表記で値に応じたカラーコードの順番に並び替えたリストを得る
colors = [color_dict[i] for i in color_dict]

for i, item in enumerate(df.columns):
    ax.plot(x, df[item], color=colors[i])
    
    # グラフの右側にラベル(凡例)を表示する
    ax.text(
        x = x.max() + timedelta(days=3),
        y = df[item][-1],
        s = item,
        va = 'center',
        color = colors[i] # 凡例とグラフの色を合わせる
    )

fig, ax = plt.subplots()

x = df.index

for _, item in enumerate(df.columns):
    ax.plot(x, df[item])
    
# x軸のtick labelを月だけにする('%-m'は0埋めなしの月表記)
ax.xaxis.set_major_formatter(mpl.dates.DateFormatter('%-m'))

fig, ax = plt.subplots()

x = df.index

for _, item in enumerate(df.columns):
    ax.plot(x, df[item])
    
# x軸のtick labelを月だけにする('%-m'は0埋めなしの月表記)
ax.xaxis.set_major_formatter(mpl.dates.DateFormatter('%-m'))

# y軸の幅を取得する
ax_bottom, ax_top = ax.get_ylim()

# x軸に垂直に引く、線の長さを調整する
# figsizeによって微調整が必要
y_coord_start = ax_bottom - (ax_top - ax_bottom)/10  # 始点
y_coord_end = ((ax_top - ax_bottom)/200) + ax_bottom # 終点(x軸と接している方)

# major tickのpositionを取得するために必要
fig.canvas.draw()

# 各年の最初の月の下に年(yyyy)を表示する
for index, (key, gr) in enumerate(df.groupby(x.year)):
    
    # 最初の月のインデックスを取得する
    i = np.where(x == gr.index[0])[0][0]
    
    # 座標を取得する
    pos = ax.get_xmajorticklabels()[i].get_position()
    
    # x軸の外側に垂直に線を引くx座標を調整する
    # 大体、pos[0]-15でいけるけど、一番最初だけ微調整が必要
    x_coord = pos[0]-6 if index==0 else pos[0]-15

    # 線と年を描画する
    ax.annotate(
        s='        {}'.format(key),        # 年
        xy=(x_coord, y_coord_end),         # 終点の位置
        xytext = (x_coord, y_coord_start), # 始点の位置
        color='dimgray',
        ha='center',
        va='top',
        arrowprops= {'arrowstyle':'-', 'color':'dimgray'}
    )

fig, ax = plt.subplots()

x = df.index

for _, item in enumerate(df.columns):
    ax.plot(x, df[item])

# y軸の目盛りを250から600までに設定し、100の倍数ずつ表示する
ax.set_ylim(250, 600)
ax.yaxis.set_major_locator(mpl.ticker.MultipleLocator(100))

TICK_COLOR = '#BDBDBD'       # グレー
CHART_MAIN_COLOR = '#6BCAB6' # 緑
CHART_SUB_COLOR = '#BDBDBD'  # グレー

from datetime import timedelta

"""
データフレーム
"""
df = pd.DataFrame(
    data = {
        'A': [464, 332, 447, 473, 376, 418, 328, 392, 327],
        'B': [409, 462, 540, 375, 412, 475, 425, 518, 526],
        'C': [306, 345, 368, 303, 485, 518, 504, 576, 401]
    },
    index = ['2019-07', '2019-08', '2019-09', '2019-10', '2019-11', '2019-12', '2020-01', '2020-02', '2020-03']
)

# インデックスを時系列データにキャストする
df.index = pd.to_datetime(df.index)


"""
時系列グラフをプロットする図を定義する
"""
fig, ax = plt.subplots(figsize=(6, 4), dpi=144)


"""
時系列グラフとしてプロットするデータ
"""
x = df.index


"""
カラーコードの設定
"""
# カラム名で条件分岐して、カラーコードの辞書(Key:カラム名、Value:カラーコード)を作成する
color_dict = {i:CHART_MAIN_COLOR if i=='B' else CHART_SUB_COLOR for i in df.columns}

# リスト内包表記で値に応じたカラーコードの順番に並び替えたリストを得る
colors = [color_dict[i] for i in color_dict]


"""
時系列グラフの重なり順の設定
"""
# カラム名で条件分岐して、重なり順の辞書(Key:カラム名、Value:重なり順)を作成する
# 数字が大きいほど上に描画される
zorder_dict = {i:2 if i=='B' else 1 for i in df.columns}

# リスト内包表記で値に応じた重なり順の順番に並び替えたリストを得る
zorders = [zorder_dict[i] for i in zorder_dict]


"""
時系列グラフをプロットする
"""
for i, item in enumerate(df.columns):
    ax.plot(x, df[item], linewidth=3, color=colors[i], zorder=zorders[i])
    
    # グラフの右側にラベル(凡例)を表示する
    ax.text(
        x = x.max() + timedelta(days=3),
        y = df[item][-1],
        s = item,
        fontsize = 12,
        va = 'center',
        color = colors[i] # 凡例とグラフの色を合わせる
    )


"""
フレームの設定
"""
# 枠線を消す
[ax.spines[side].set_visible(False) for side in ['left', 'right', 'top']]

# 枠線の色を変更する
ax.spines['bottom'].set_color(TICK_COLOR)    
    
# 目盛りラベルと目盛りの色を変更する
ax.tick_params(colors=TICK_COLOR)


"""
y軸の設定
"""
# Y軸の目盛りを消す
ax.tick_params(left=False)

# y軸の目盛りを250から600までに設定し、100の倍数ずつ表示する
ax.set_ylim(250, 600)
ax.yaxis.set_major_locator(mpl.ticker.MultipleLocator(100))

# y軸にグリッド線を表示する
ax.grid(axis='y', ls='--', lw=0.5, color=TICK_COLOR)    


"""
x軸の設定
"""
# x軸のtick labelを月だけにする('%-m'は0埋めなしの月表記)
ax.xaxis.set_major_formatter(mpl.dates.DateFormatter('%-m'))

# y軸の幅を取得する
ax_bottom, ax_top = ax.get_ylim()

# x軸に垂直に引く、線の長さを調整する
# figsizeによって微調整が必要
y_coord_start = ax_bottom - (ax_top - ax_bottom)/10  # 始点
y_coord_end = ((ax_top - ax_bottom)/150) + ax_bottom # 終点(x軸と接している方)

# major tickのpositionを取得するために必要
fig.canvas.draw()

# 各年の最初の月の下に年(yyyy)を表示する
for index, (key, gr) in enumerate(df.groupby(x.year)):
    
    # 最初の月のインデックスを取得する
    i = np.where(x == gr.index[0])[0][0]
    
    # 座標を取得する
    pos = ax.get_xmajorticklabels()[i].get_position()
    
    # x軸の外側に垂直に線を引くx座標を調整する
    # 大体、pos[0]-15でいけるけど、一番最初だけ微調整が必要
    x_coord = pos[0]-6 if index==0 else pos[0]-15

    # 線と年を描画する
    ax.annotate(
        s='        {}'.format(key),        # 年
        xy=(x_coord, y_coord_end),         # 終点の位置
        xytext = (x_coord, y_coord_start), # 始点の位置
        color=TICK_COLOR,
        ha='center',
        va='top',
        arrowprops= {'arrowstyle': '-', 'color': TICK_COLOR, 'lw': 0.5}
    ) 

横棒グラフ

下準備として、こんな感じのpandas.DataFrameを作成します。

df = pd.DataFrame(
    data = {'販売個数': [314, 365, 396, 436, 409, 424, 377]},
    index = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
)
print(df)
#    販売個数
# A   314
# B   365
# C   396
# D   436
# E   409
# F   424
# G   377

fig, ax = plt.subplots()

x = df['販売個数'] # x軸の値
y = df.index      # y軸の値

# 横棒グラフをプロットする
ax.barh(y=y, width=x)

# 販売個数カラムで降順ソート
df = df.sort_values(by=['販売個数'], ascending=False)
#    販売個数
# D   436
# F   424
# E   409
# C   396
# G   377
# B   365
# A   314

fig, ax = plt.subplots()

x = df['販売個数']
y = df.index

ax.barh(y=y, width=x)

# y軸を反転させる(配列の最初から描画されるため)
ax.invert_yaxis()

fig, ax = plt.subplots()

x = df['販売個数']
y = df.index

# colorパラメータにカラーコードを指定する
ax.barh(y=y, width=x, color='#6BCAB6')

fig, ax = plt.subplots()

x = df['販売個数']
y = df.index

# インデックス毎に任意のカラーコードを指定する
color_dict = {
    'A': '#BDBDBD',
    'B': '#15607A', # 青
    'C': '#BDBDBD',
    'D': '#BDBDBD',
    'E': '#6BCAB6', # 緑
    'F': '#BDBDBD',
    'G': '#BDBDBD'
}

# リスト内包表記で値に応じたカラーコードの順番に並び替えたリストを得る
colors = [color_dict[i] for i in color_dict]

# colorパラメータにカラーコードの配列を指定
ax.barh(y=y, width=x, color=colors)

辞書内包表記を使えば、条件に応じた動的な辞書も作成できます

どっちも同じ
# 辞書内包表記
color_dict = {i:'#15607A' if i=='B' else '#6BCAB6' if i=='E' else '#BDBDBD' for i in df.index}

# 普通の辞書の作成方法
color_dict = {
    'A': '#BDBDBD',
    'B': '#15607A', # 青
    'C': '#BDBDBD',
    'D': '#BDBDBD',
    'E': '#6BCAB6', # 緑
    'F': '#BDBDBD',
    'G': '#BDBDBD'
}

420を超えていれば緑色でプロット。350未満なら赤色でプロット。

fig, ax = plt.subplots()

x = df['販売個数']
y = df.index

ax.barh(y=y, width=x)

# 条件分岐で実際の値ごとにカラーコードを設定し、横棒グラフの色を変更する
# Good : 緑、 Bad : 赤
color_dict = {i:'#C71D1D' if i<350 else '#6BCAB6' if i>420 else '#BDBDBD' for i in x}

colors = [color_dict[i] for i in color_dict]
ax.barh(y=y, width=x, color=colors)

fig, ax = plt.subplots()

x = df['販売個数']
y = df.index

ax.barh(y=y, width=x)

# 水平方向の最大値を得る
hmax = x.max()

# 実際の値を表示する
for i, value in enumerate(x):
    ax.text(
        x = value + hmax*0.01,    # x座標。バーからちょっと離れたところに表示させる
        y = i,                    # y座標
        s = '{:,}'.format(value), # 表示する値。数値の場合、3桁区切りにしたほうが親切
        va = 'center',            # 垂直方向の整列
        ha = 'left'               # 水平方向の整列。内側に実際の値を表示したい場合は、rightにする
    )

fig, ax = plt.subplots()

x = df['販売個数']
y = df.index

ax.barh(y=y, width=x)

# 合計値を得る
total = x.sum()

hmax = x.max()
for i, value in enumerate(x):
    ax.text(
        x = value + hmax*0.01,             # x座標。バーからちょっと離れたところに表示させる
        y = i,                             # y座標
        s = '{:,.2%}'.format(value/total), # 表示する値。少数第2位のパーセンテージ表記
        va = 'center',                     # 垂直方向の整列
        ha = 'left'                        # 水平方向の整列。内側に実際の値を表示したい場合は、rightにする
    )

TICK_COLOR = '#BDBDBD'         # グレー
CHART_GOOD_COLOR = '#6BCAB6'   # 緑
CHART_BAD_COLOR = '#C71D1D'    # 赤
CHART_NORMAL_COLOR = '#BDBDBD' # グレー


"""
データフレーム
"""
df = pd.DataFrame(
    data = {'販売個数': [314, 365, 396, 436, 409, 424, 377]},
    index = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
)


"""
横棒グラフをプロットする図を定義する
"""
fig, ax = plt.subplots(figsize=(6, 4), dpi=144)


"""
横棒グラフとしてプロットするデータ
"""
x = df['販売個数'] # x軸の値
y = df.index      # y軸の値


"""
横棒グラフの色の設定
"""
# 条件分岐で実際の値ごとにカラーコードを設定し、横棒グラフの色を変更する
# Good : 緑、 Bad : 赤
color_dict = {i:CHART_BAD_COLOR if i<350 else CHART_GOOD_COLOR if i>420 else CHART_NORMAL_COLOR for i in x}

# リスト内包表記で値に応じたカラーコードの順番に並び替えたリストを得る
colors = [color_dict[i] for i in color_dict]


"""
横棒グラフをプロットする
"""
ax.barh(y=y, width=x, color=colors, height=0.75)


"""
フレームの設定
"""
# タイトルを付ける
fig.suptitle(t='製品D,Fは目標達成!!製品Aは未到達', color='#666666')

# サブタイトルを付ける
ax.set_title(label='製品毎の販売個数比較', loc='center', pad=None, color='#BDBDBD', size='small')

# 枠線を消す
[ax.spines[side].set_visible(False) for side in ['left', 'right', 'top', 'bottom']]

# 目盛り、目盛りラベルを消す
ax.tick_params(labelbottom=False, bottom=False, left=False)

# y軸を反転させる(配列の最初から描画されるため)
ax.invert_yaxis()


"""
y軸の設定
"""
# y軸ラベルを水平方向でセンター寄せする
for tick in ax.yaxis.get_major_ticks():
    tick.label1.set_horizontalalignment('center')

# 横棒グラフとラベルの距離を調整する
ax.tick_params(axis='y', pad=4)

# 目盛りラベルを装飾する
ax.tick_params(axis='y', labelcolor=TICK_COLOR, labelsize='small')


"""
実際の数値を横棒グラフの右に表示させる
"""
hmax = x.max()
for i, value in enumerate(x):
    ax.text(
        x = value + hmax*0.01,    # x座標。バーからちょっと離れたところに表示させる
        y = i,                    # y座標
        s = '{:,}'.format(value), # 表示する値。
        va = 'center',            # 垂直方向の整列
        ha = 'left',              # 水平方向の整列。内側に実際の値を表示したい場合は、rightにする
        size = 'small',           # 文字のサイズ
        color = colors[i]         # 文字の色をグラフと同じにする
    )

積上げ横棒グラフ

100%積上げ横棒グラフ