morikomorou’s blog

自分が学んだことなどの備忘録的なやつ

【python】matplotlibでパレート図を作成する方法


はじめに

製造業でよく使われるQC七つ道具の一つであるパレート図と呼ばれるものを作成してみようと思います。
パレート図とは、値が降順にプロットされた棒グラフとその累積構成比を表す折れ線グラフを組み合わせた複合グラフのことです。

品質管理において、欠陥の最も一般的な原因、最も起こりやすい欠陥の種類などを見つけ出すために使われます。
Excelでも一発でパレート図を作成できますが、なんか見栄えが悪かったり、見栄えを整えようとすると意外と大変だったりするので、pythonで作成してみます
関数化して一瞬でグラフが作れるようにしたいと思います。




データの準備

プロット用のデータを準備しておきます。
以下の表のデータをサンプルとして使用しますので、data.csvとして保存しておきます。

ラベル カウント
APPLE 20
BANANA 40
CACAO 80
DURIAN 5
EGG PLANT 15

データの加工

データの読み込みを行い、パレート図プロット用に、データを少々加工します
行うのは下記の2点。

  1. カウントで降順に並べ替え
  2. カウントの累積割合を算出
import numpy as np
import pandas as pd

data = pd.read_csv('data.csv', encoding='shift-jis')
data = data.sort_values(by='カウント', ascending=False)
sum_data = data['カウント'].sum()
data['accum'] = np.cumsum(data['カウント'])                      # 累積を算出
data['accum_ratio'] = data['accum'] / sum_data * 100          # 累積割合を算出

加工後のデータは以下のようになります。

ラベル カウント accum accum_ratio
2 CACAO 80 80 50.000
1 BANANA 40 120 75.000
0 APPLE 20 140 87.500
4 EGG PLANT 15 155 96.875
3 DURIAN 5 160 100.000

パレート図のプロット

まずは何も考えずそのままプロットしてみます。
左右に2つの軸のあるグラフを作る際はax.twinx()を使用します。
fig, ax = plt.subplots()
で作成したaxに対し、twin_ax = ax.twinx()
とすることで、x軸共通でy軸のみ異なるグラフを作成できます。

import matplotlib.pyplot as plt
import seaborn as sns

sns.set()
plt.rcParams["font.family"] = "IPAexGothic"

labels = len(data)
fig, ax = plt.subplots()
ax.bar(data['ラベル'], data['カウント'], label='カウント')
ax.set_xlabel("ラベル")
ax.set_ylabel("カウント")
ax.set_ylim([0, sum_data])
ax.grid(axis='x')     # yのグリッドは邪魔なので消しておく

twin_ax=ax.twinx()
twin_ax.plot(range(labels), data['accum_ratio'], marker='o', color='orange', label='累計比率')
twin_ax.set_ylabel('累計比率')
twin_ax.set_ylim([0, 100])
twin_ax.grid(False)     # 棒グラフの上からグリッドができてしまうので第2Y軸の方のグリッドは消しておく
fig.legend(loc="center right", bbox_to_anchor=(1,0.5), bbox_transform=ax.transAxes)
plt.tight_layout()
plt.show()

結果はこちら。


見栄えを整える

もう少し見やすくしたいと思います。




折れ線グラフと棒グラフの位置関係の整理

以下2点変更します

  • 折れ線グラフは0%始まりにする
  • 棒グラフの右上の角と折れ線グラフのマーカーが交差するように変更する
accum_data = [0] + data['accum_ratio'].tolist()  # 累計比率のデータの先頭に0追加
fig, ax = plt.subplots()
bar = ax.bar(range(labels), data['カウント'], align="edge", width=1, label='カウント')
ax.set_ylim([0, sum_data])
ax.set_xlim([0, labels])
ax.set_xticks([0.5 + i for i in range(labels)])  # 棒グラフの真ん中にX軸ラベルが来るように変更
ax.set_xticklabels(data['ラベル'].tolist(),rotation = -90)  # 棒グラフの真ん中にX軸ラベルが来るように変更
ax.grid(axis='x')
ax.set_xlabel("ラベル")
ax.set_ylabel("カウント")

twin_ax=ax.twinx()
twin_ax.plot(range(labels + 1), accum_data, marker='o', color='orange', label='累計比率', linewidth=2.0)
twin_ax.set_ylabel('累計比率')
twin_ax.set_ylim([0, 100])
twin_ax.grid(False)
fig.legend(loc="center right", bbox_to_anchor=(1,0.5), bbox_transform=ax.transAxes)
plt.tight_layout()
plt.show()

結果はこちら。

棒グラフの右上の角と折れ線グラフがきれいに交差するようになりましたね。

左右の軸のグリッドを合わせる

どちらも10等分して目盛り線を合わせます。

percent_labels = [str(i) + "%" for i in np.arange(0, 100+1, 10)]  # 累計比率表示用リスト
accum_data = [0] + data['accum_ratio'].tolist()
fig, ax = plt.subplots()
bar = ax.bar(range(labels), data['カウント'], align="edge", width=1, label='カウント')
ax.set_ylim([0, sum_data])
ax.set_xlim([0, labels])
ax.set_xticks([0.5 + i for i in range(labels)])
ax.set_xticklabels(data['ラベル'].tolist(),rotation = -90)
ax.set_yticks(np.arange(0, sum_data+1, sum_data / 10))  # 第1Y軸を10等分して表示
ax.grid(axis='x')
ax.set_xlabel("ラベル")
ax.set_ylabel("カウント")

twin_ax=ax.twinx()
twin_ax.plot(range(labels + 1), accum_data, marker='o', color='orange', label='累計比率', linewidth=2.0)
twin_ax.set_ylabel('累計比率')
twin_ax.set_ylim([0, 100])
twin_ax.set_yticks(np.arange(0, 100+1, 10))  # 第2Y軸を10等分して表示
twin_ax.set_yticklabels(percent_labels)
twin_ax.grid(False)
fig.legend(loc="center right", bbox_to_anchor=(1,0.5), bbox_transform=ax.transAxes)
plt.tight_layout()
plt.show()


棒グラフの左右に補助線追加

折れ線との関係がわかりやすいように棒グラフの左右にグリッド追加します。
minorのgridを使用して補助線を入れます。

percent_labels = [str(i) + "%" for i in np.arange(0, 100+1, 10)]
accum_data = [0] + data['accum_ratio'].tolist()
fig, ax = plt.subplots()
bar = ax.bar(range(labels), data['カウント'], align="edge", width=1, label='カウント')
ax.set_ylim([0, sum_data])
ax.set_xlim([0, labels])
ax.set_xticks([0.5 + i for i in range(labels)])
ax.set_xticks([1 + i for i in range(labels)], minor=True) #副目盛り
ax.set_xticklabels(data['ラベル'].tolist(),rotation = -90)
ax.set_yticks(np.arange(0, sum_data+1, sum_data / 10))
ax.grid(axis='x')
ax.grid(axis='x', ls=':', which='minor') #副目盛り
ax.set_xlabel("ラベル")
ax.set_ylabel("カウント")

twin_ax=ax.twinx()
twin_ax.plot(range(labels + 1), accum_data, marker='o', color='orange', label='累計比率', linewidth=2.0)
twin_ax.set_ylabel('累計比率')
twin_ax.set_ylim([0, 100])
twin_ax.set_yticks(np.arange(0, 100+1, 10))
twin_ax.set_yticklabels(percent_labels)
twin_ax.grid(False)
fig.legend(loc="center right", bbox_to_anchor=(1,0.5), bbox_transform=ax.transAxes)
plt.tight_layout()
plt.show()


関数化

一発でグラフが書けるように最後に関数化しておきます。
引数にはプロット用データのデータフレーム、ラベルの列名、カウントの列名を指定してグラフ表示まで行います。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns


def pareto(df, label_name, cnt_name):
    ''' plot pareto chart
    input
      df: data for plotting (DataFrame)
      label_name: row name for label (str)
      cnt_name: row name for count (str)
    '''
    sns.set()
    plt.rcParams["font.family"] = "IPAexGothic"
    label = df[label_name].tolist()
    cnt = df[cnt_name].tolist()
    data = pd.DataFrame(label, columns=['ラベル'])
    data['カウント'] = cnt
    data = data.sort_values(by='カウント', ascending=False)
    sum_data = data['カウント'].sum()
    data['accum'] = np.cumsum(data['カウント'])
    data['accum_ratio'] = data['accum'] / sum_data * 100

    labels = len(data)

    percent_labels = [str(i) + "%" for i in np.arange(0, 100+1, 10)]
    accum_data = [0] + data['accum_ratio'].tolist()
    fig, ax = plt.subplots()
    bar = ax.bar(range(labels), data['カウント'], align="edge", width=1, label=cnt_name)
    ax.set_ylim([0, sum_data])
    ax.set_xlim([0, labels])
    ax.set_xticks([0.5 + i for i in range(labels)])
    ax.set_xticks([1 + i for i in range(labels)], minor=True) #副目盛り
    ax.set_xticklabels(data['ラベル'].tolist(),rotation = -90)
    ax.set_yticks(np.arange(0, sum_data+1, sum_data / 10))
    ax.grid(axis='x')
    ax.grid(axis='x', ls=':', which='minor') #副目盛り
    ax.set_xlabel(label_name)
    ax.set_ylabel(cnt_name)

    twin_ax=ax.twinx()
    twin_ax.plot(range(labels + 1), accum_data, marker='o', color='orange', label='累計比率', linewidth=2.0)
    twin_ax.set_ylabel('累計比率')
    twin_ax.set_ylim([0, 100])
    twin_ax.set_yticks(np.arange(0, 100+1, 10))
    twin_ax.set_yticklabels(percent_labels)
    twin_ax.grid(False)
    fig.legend(loc="center right", bbox_to_anchor=(1,0.5), bbox_transform=ax.transAxes)
    plt.tight_layout()
    plt.show()


data = pd.read_csv('data.csv', encoding='shift-jis')
pareto(data, 'ラベル', 'カウント')

おわりに

パレート図が一発で書けるようになりました。
毎回エクセルのグラフの設定に悩まなくて済みそうです。