Pythonでパレート図を作成する方法【Matplotlib】

Pythonでパレート図を作成する方法【Matplotlib】 プログラミング

次のパレート図は、ウィキペディア(Wikipedia)の「パレート分析」にあるモノです。
この記事では、このパレート図と同じものを作成していきます。
以下では、このパレート図を「ゴールのパレート図」と呼びます。

この記事を読めば、 パレート図を作成できるようになります。
本記事は次の構成で進めていきます。

本記事の内容

  • Pythonでパレート図を作成するための環境
  • とりあえず、Pythonでパレート図を作成する
  • Matplotlibで作成したパレート図を調整する
  • Pythonでパレート図を作成する方法のまとめ

Pythonでパレート図を作成するための環境

  • Windows 10 Home (バージョン1909)※以下の説明は64bit前提
  • Python 3.7.3
  • NumPy 1.18.5
  • Matplotlib 3.2.1
  • Pandas 0.24.2

Pythonの上記ライブラリは、すべてインストールする必要があります。
インストールしていないライブラリは、以下のコマンドでインストールしてください。

pip install numpy
pip install matplotlib
pip install pandas

作成したグラフにおいて、日本語が「□□□」と表示される場合があります。
この場合は、Matplotlibが日本語対応できていません。
日本語が化ける問題を解消したいなら、次の記事をご覧ください。

とりあえず、Pythonでパレート図を作成する

「とりあえず」、Pythonで簡単にパレート図を作成します。
パレート図と言っても、難しく考える必要はありません。

パレート図は、棒グラフと折れ線グラフを合体しているだけです。
そのため、棒グラフの作成と折れ線グラフの作成を理解していることが必要となります。
もし、それらの理解が不足しているなら、以下の記事を参考にしてください。

モジュールを読み込む

パレート図を作成するために必要なモジュールをimportします。
コードは以下。

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

データを用意する

元データを用意する

ウィキペディア日本語版の記事「パレート分析」ページより。
下記は、「Aグループを70、Bグループを90、Cグループを100(構成比率の累計)としたグループ分けの例」の表よち。

これが、「ゴールのパレート分析」のデータとなります。






項目頻度構成比率比率の累計グループ
10018.218.2A
9016.434.6A
8014.549.1A
7012.761.8A
6010.972.7B
509.181.8B
407.389.1B
305.594.5C
203.698.2C
101.8100.0C
合計550100.0

上記からは、「項目」と「頻度」を利用します。
「比率の累計」は、「頻度」から算出します。

リスト型(list型)のデータを用意します。
10行のデータであるため、そのままコードに書きます。
もっと数の多いデータであれば、CSVなどのファイルに保存して読み込む形を取ります。

# ラベル
labels = ['あ', 'い', 'う', 'え', 'お', 'か', 'き', 'く', 'け', 'こ']
# 値
values = [100, 90, 80, 70, 60 ,50, 40, 30, 20, 10]

そして、用意したデータからデータフレームを作成します。
データフレームにしておけば、データの操作が容易となります。
早速、降順にデータを並べています(今回はもともと降順ですが、そうではない場合も多いです)。

# データ操作用
df = pd.DataFrame({"label": labels, "value":values}, columns=["label", "value"])
# 「値」の降順にデータを並び替える
df = df.sort_values(by="value", ascending=False)

この時点でのdfの内容は、以下。


labelvalue
0100
190
280
370
460
550
640
730
820
910

累積和を用意する

コードは以下。

# 累積和を求める
df["accum"] = np.cumsum(df["value"])

累積和を追加したdfの内容は、以下。


labelvalueaccum
0100100
190190
280270
370340
460400
550450
640490
730520
820540
910550

比率の累計を用意する

「パレート分析」ページにはもともと用意されていた「比率の累計」です。
通常は、自分で求める必要があります。

比率の累計を求めるコードは、以下。

# 比率の累計を求める
df["accum_percent"] = df["accum"] / sum(df["value"]) * 100

比率の累計を追加したdfの内容は、以下。


labelvalueaccumaccum_percent
010010018.1818181818
19019034.5454545455
28027049.0909090909
37034061.8181818182
46040072.7272727273
55045081.8181818182
64049089.0909090909
73052094.5454545455
82054098.1818181818
910550100

これで、必要なデータを準備できました。
では、実際にパレート図を作成していきましょう。

パレート図

パレート図は、棒グラフと折れ線グラフを合体させたモノでしたね。
これをイメージしやすいように、まずは段階を踏んで作成していきましょう。

棒グラフの描画

まずは、棒グラフの作成です。
追加したコードは以下。

# サイズ指定
fig = plt.figure()
# 軸関係の表示
ax = fig.add_subplot(111)

# データ数のカウント
data_num = len(df)

# 棒グラフの描画
ax.bar(range(data_num), df["value"])
ax.set_xticks(range(data_num))
ax.set_xticklabels(df["label"].tolist())
ax.set_xlabel("(項目)")
ax.set_ylabel("(頻度)")

棒グラフだけを表示するコードの実行結果は以下。

棒グラフが表示されていますね。
では、次はここに折れ線グラフを加えていきます。

折れ線グラフの描画

ポイントは、「ax_add = ax.twinx()」です。
ここで「ax」は棒グラフを描いている軸と言えます。
その軸に「ax_add」という軸を追加しています。

# 折れ線グラフの描画
ax_add = ax.twinx()
ax_add.plot(range(data_num), df["accum_percent"])
ax_add.set_ylim([0, 100])
ax_add.set_ylabel("(比率の累計)")

折れ線グラフを加えた結果は以下。

確かに折れ線が追加されていますね。
でも、同じ色なのでわかくにくいです。

以上がパレート図を作成する方法の説明です。
やはり、棒グラフと折れ線グラフが描くだけですね。
あとは、このわかりにくいパレート図を「ゴールのパレート図」に近づけていきます。

グラフを画像ファイルに保存する

作成したパレート図を画像ファイルに保存します。
facecolorは、背景色です。
何も指定しなければ、「白」となります。

# グラフを画像保存
plt.savefig("result.png", facecolor="white")

「とりあえず、Pythonでパレート図を作成する」のまとめ

ここまでの処理をまとめると、以下のコードになります。

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

# ラベル
labels = ['あ', 'い', 'う', 'え', 'お', 'か', 'き', 'く', 'け', 'こ']
# 値
values = [100, 90, 80, 70, 60 ,50, 40, 30, 20, 10]

# データ操作用
df = pd.DataFrame({"label": labels, "value": values}, columns=["label", "value"])
# 「値」の降順にデータを並び替える
df = df.sort_values(by="value", ascending=False)
# 累積和を求める
df["accum"] = np.cumsum(df["value"])
# 比率の累計を求める
df["accum_percent"] = df["accum"] / sum(df["value"]) * 100

# サイズ指定
fig = plt.figure()
# 軸関係の表示
ax = fig.add_subplot(111)

# データ数のカウント
data_num = len(df)

# 棒グラフの描画
ax.bar(range(data_num), df["value"])
ax.set_xticks(range(data_num))
ax.set_xticklabels(df["label"].tolist())
ax.set_xlabel("(項目)")
ax.set_ylabel("(頻度)")

# 折れ線グラフの描画
ax_add = ax.twinx()
ax_add.plot(range(data_num), df["accum_percent"])
ax_add.set_ylim([0, 100])
ax_add.set_ylabel("(比率の累計)")

# グラフを画像保存
plt.savefig("result.png", facecolor="white")

Matplotlibで作成したパレート図を調整する

基本的には、以下の記事の内容でカバーできます。
詳細を知りたい場合は、各記事を参照してください。

では、一つづつ片付けていきましょう。

画像サイズを合わせる

ゴールのパレート図サイズは、横780px・縦544pxです。
対応するコードは以下。

# サイズ指定
fig = plt.figure(dpi=100, figsize=(7.80, 5.44))

折れ線グラフの色を変更する

plotの引数にcolorを追加。

ax_add.plot(range(data_num), df["accum_percent"], color="#ff8181")

折れ線グラフの線幅を変更する

plotの引数にlinewidthを追加。

ax_add.plot(range(data_num), df["accum_percent"], color="#ff8181", linewidth=2.5)

折れ線グラフの各点をマーカー「〇」で表示する

markerに「o」を設定します。

ax_add.plot(range(data_num), df["accum_percent"], color="#ff8181", linewidth=2.5, marker="o")

この時点でのパレート図を確認します。

マーカーのアウトラインを変更する必要がありますね。
次でマーカーのアウトラインを変更します。

折れ線グラフのマーカーのアウトラインを変更する

markersizeでマーカーの大きさを指定。
markeredgecolorで色を指定します。
markeredgewidthで線幅の指定です。

ax_add.plot(range(data_num), df["accum_percent"], color="#ff8181", linewidth=2.5,
            marker="o",markersize=8, markeredgecolor="#000081", markeredgewidth=0.7)

ここで、アウトライン変更を確認します。

変更できましたね。
ただ、画像の表示サイズによっては、わかりにくいかもしれません。

棒グラフのサイズを変更する

widthで横幅の指定です。

ax.bar(range(data_num), df["value"], width=0.4)

棒グラフの色を変更する

colorで横幅の指定です。

ax.bar(range(data_num), df["value"], width=0.4, color="#3366ff")

棒グラフのアウトラインの色を変更する

edgecolorで横幅の指定です。

ax.bar(range(data_num), df["value"], width=0.4, color="#3366ff", edgecolor="#0f1e4b")

y軸の表示範囲を調整する

設定しないと、データの最大値によって自動的に決まります。
「ゴールのパレート図」に近づけるため、ここでは設定します。

棒グラフのy軸(左)

120に設定します。

ax.set_ylim([0, 120])

折れ線グラフのy軸(右)

100をy軸(折れ線グラフ)の最大に設定しています。
そのため、下図のように不細工な表示になっています。

すべて表示させるために、y軸を120に設定し直します。
100から120に変更しただけです。

ax_add.set_ylim([0, 120])

y軸変更後のパレート図は以下。

こういう細かいテクニックもグラフ表示には必要なのでしょう。

折れ線グラフのy軸の目盛りを変更する

目盛りの表示が、小数点表示です。
同じように小数点にフォーマットします。

まず、フォーマット用にモジュールを読み込みます。

from matplotlib.ticker import FormatStrFormatter

これをax_add宣言以降に追加。

# 軸目盛りのフォーマット
ax_add.yaxis.set_major_formatter(FormatStrFormatter("%.1f"))

凡例を表示する

1軸の凡例の応用版ですね。
凡例は、1軸でも少しわかりにくいところがあります。
その場合は、以下をご覧ください。

2軸の凡例は以下のコードで実現します。

# 凡例
# それぞれの軸でデータとラベルを抽出
ax_handler, ax_label = ax.get_legend_handles_labels()
ax_add_handler, ax_add_label = ax_add.get_legend_handles_labels()
# 凡例をまとめて出力
ax.legend(ax_handler + ax_add_handler, ax_label + ax_add_label, bbox_to_anchor=(1.13, 0.55), loc='upper left')

get_legend_handles_labelsを機能させるために以下の対応が必要です。
それぞれの引数のlabelを追加しています。

ax.bar(range(data_num), df["value"], width=0.4, label="頻度")
ax_add.plot(range(data_num), df["accum_percent"], color="#ff8181", linewidth=2.5,
            marker="o",markersize=8, markeredgecolor="#000081", markeredgewidth=0.7,
            label="比率の累計")

余白を調整する

ゴールのパレート図と同じような余白にします。

# 余白調整
plt.subplots_adjust(left=0.08, right=0.73, bottom=0.1, top=0.96)

この調整に伴い、凡例の位置を調整。

ax.legend(ax_handler + ax_add_handler, ax_label + ax_add_label, bbox_to_anchor=(1.15, 0.55), loc='upper left')

背景色を変更する

本記事では、背景色の変更は対応しません。
グラデーション対応をしようとする、そうそう簡単ではないようです。
これを解説しようとすると、主目的であるパレート図の作成から逸脱してしまいます。

「グラデーション対応をどうしてもやりたい」と言う方は、以下を参考にしてください。
https://matplotlib.org/examples/pylab_examples/gradient_bar.html
https://matplotlib.org/3.1.1/gallery/lines_bars_and_markers/gradient_bar.html

Pythonでパレート図を作成する方法のまとめ

まず、ここまでの修正を取り込んだコードを表示します。

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import FormatStrFormatter

# ラベル
labels = ['あ', 'い', 'う', 'え', 'お', 'か', 'き', 'く', 'け', 'こ']
# 値
values = [100, 90, 80, 70, 60 ,50, 40, 30, 20, 10]

# データ操作用
df = pd.DataFrame({"label": labels, "value": values}, columns=["label", "value"])
# 「値」の降順にデータを並び替える
df = df.sort_values(by="value", ascending=False)
# 累積和を求める
df["accum"] = np.cumsum(df["value"])
# 比率の累計を求める
df["accum_percent"] = df["accum"] / sum(df["value"]) * 100

# サイズ指定
fig = plt.figure(dpi=100, figsize=(7.80, 5.44))
# 軸関係の表示
ax = fig.add_subplot(111)

# データ数のカウント
data_num = len(df)

# 棒グラフの描画
ax.bar(range(data_num), df["value"], width=0.4, color="#3366ff", edgecolor="#0f1e4b", label="頻度")
ax.set_xticks(range(data_num))
ax.set_xticklabels(df["label"].tolist())
ax.set_xlabel("(項目)")
ax.set_ylabel("(頻度)")
ax.set_ylim([0, 120])

# 折れ線グラフの描画
ax_add = ax.twinx()
ax_add.plot(range(data_num), df["accum_percent"], color="#ff8181", linewidth=2.5,
            marker="o",markersize=8, markeredgecolor="#000081", markeredgewidth=0.7,
            label="比率の累計")
ax_add.set_ylim([0, 120])
ax_add.set_ylabel("(比率の累計)")
# 軸目盛りのフォーマット
ax_add.yaxis.set_major_formatter(FormatStrFormatter("%.1f"))

# 凡例
# それぞれの軸でデータとラベルを抽出
ax_handler, ax_label = ax.get_legend_handles_labels()
ax_add_handler, ax_add_label = ax_add.get_legend_handles_labels()
# 凡例をまとめて出力
ax.legend(ax_handler + ax_add_handler, ax_label + ax_add_label, bbox_to_anchor=(1.15, 0.55), loc='upper left')

# 余白調整
plt.subplots_adjust(left=0.08, right=0.73, bottom=0.1, top=0.96)

# グラフを画像保存
plt.savefig("result.png", facecolor="white")

この実行結果は以下。

背景色以外は、ゴールのパレート図を完コピできました。
そもそも、背景色は白がベストだと思います。

タイトルとURLをコピーしました