Matplotlibで棒グラフの表示を微調整する【Python】

Matplotlibで棒グラフの表示を微調整する【Python】 プログラミング

いきなりですが、次の棒グラフをご覧ください。

初級レベルの棒グラフ

これは、Matplotlibを利用してPythonで作成しています。
一見すると、簡単に作れる棒グラフに見えるかもしれません。

しかし、意外とそうではありません。
Matplotlibを利用すれば、確かに簡単に棒グラフを作成できます。

次の記事では、Matplotlibで簡単に棒グラフを作成できる方法を説明しています。
実際に動くプログラムのコード付きです。

上の記事だけではなく、ネット上に多くの参考記事が存在しています。
それらを見れば、Matplotlibで簡単に棒グラフを作成できるでしょう。

しかし、それらを参考にしても、冒頭の棒グラフは簡単には作成できません。
表示の微調整を行おうとした途端、参考になる情報がほとんどありません。
日本語では、特に参考となる情報がありません。

そこで、冒頭の棒グラフを作成できるような情報をまとめました。
つまり、この記事は中級者向けレベル(Matplotlibによる棒グラフ作成)の内容となります。

本記事の内容

  • 初級レベルで作成した棒グラフと比較する
  • Matplotlibで画像サイズを指定する
  • Matplotlibでタイトル、ラベルの文字サイズを指定する
  • Matplotlibで縦軸(y軸)ラベルの表示(位置・角度)を指定する
  • Matplotlibで目盛りの間隔を指定する
  • Matplotlibでグラフの余白を指定する
  • Matplotlibでグラフにではなく図全体にタイトルをつける

まずは、初級レベルと中級レベルの棒グラフの比較を行います。

初級レベルで作成した棒グラフと比較する

初級レベルで作成した棒グラフに関しては、次の記事を参考にしてください。
記事内のグラフAです。

上の記事の内容を簡単に説明します。
記事内では、次の棒グラフ(wikipedia「棒グラフ」より)をMatplotlibで再現しようとしています。

そして、その結果が「初級レベル」の棒グラフです。
記事では、Matplotlibで簡単に棒グラフを作成することが主眼です。
そのため、複雑な処理は避けました。

そして、今回の記事ではその複雑な処理も対応するという流れです。
その結果作成した棒グラフが、「中級レベル」の棒グラフとなります。

初級レベル

初級レベルの棒グラフ

中級レベル

中級レベルの棒グラフ

見た瞬間、初級レベルはy軸のラベルが気になりませんか?
私は、これがどうしても嫌で嫌でなんとかしたくなりました。

ただ、ネットで公開されているMatplotlibで作成した棒グラフは、これを放置したままです。
ほぼ全部がそうです。

あとは、画像サイズでしょうか。
初級レベルでは、画像のサイズ指定をしていません。
ただ、この画像サイズの指定が簡単ではありません。

残りは、細かい話になりますが、y軸の目盛りの間隔ですね。
初級レベルでは、自動的に表示されています。

こうやって比較した結果、対応すべき箇所を洗い出しました。
それが以下。

  • Matplotlibで画像サイズを指定する
  • Matplotlibでタイトル、ラベルの文字サイズを指定する
  • Matplotlibで縦軸(y軸)ラベルの表示(位置・角度)を指定する
  • Matplotlibで目盛りの間隔を指定する
  • Matplotlibでグラフの余白を指定する
  • Matplotlibでグラフにではなく図全体にタイトルをつける

初級レベルでも問題ないと言えば、問題はありません。
ただ、出来の良い棒グラフとは決して言えないです。

より出来の良いグラフにするために、上記で上げた対応を行ないます。

なお、初級レベルの棒グラフを作成したコードは以下。
これを改良していく形により、中級レベルの棒グラフを作成していきます。

import numpy as np
import matplotlib.pyplot as plt

labels = ['EUL', 'PES', 'EFA', 'EDD', 'ELDR', 'EPP', 'UEN', 'その他']
values = [39, 200, 42, 15, 67, 276, 27, 66]
lefts = np.arange(len(values))

plt.bar(lefts, values, tick_label=labels, width=0.5, color="#b2b2b2")
plt.title("欧州議会選挙 2004")
plt.xlabel("政党")
plt.ylabel("議席数")

plt.gca().spines['right'].set_visible(False)
plt.gca().spines['top'].set_visible(False)

Matplotlibで画像サイズを指定する

求める画像(wikipedeiaの棒グラフ)サイズは、横480px、縦240pxです。

追加コードは以下。
追加場所は、変数宣言のすぐ下。
figsizeの値以外は、おまじないのようなものだと考えても大丈夫です。
※画像内に1つのグラフを表示する場合

fig = plt.figure(dpi=100, figsize=(4.8,2.4))
ax = fig.add_subplot(111)

実行した結果。

dpiを変更してサイズしてした棒グラフ

サイズは、大きくなりました。
dpi=100は、固定で考えてください。
デフォルトは、dpi=72になるようです。

dpi=100にしていれば、figsizeの指定が簡単です。
480pxであれば、4.8と指定するだけです。

だから、figsize=(4.8,2.4)は横480px、縦240pxと指定することになります。
結構、このあたりの説明の情報もなく、画像サイズの指定すら困難です。

高解像度の画像が必要な場合は、dpi=500などにしてください。
その後、適当にサイズを調整します。

Matplotlibでタイトル、ラベルの文字サイズを指定する

画像サイズを変更したことにより、文字サイズも修正します。
変更・追加したコードは以下。

plt.title("欧州議会選挙 2004", fontsize=9)
plt.xlabel("政党", fontsize=8)
plt.ylabel("議席数", fontsize=8)

plt.xticks(fontsize=8)
plt.yticks(fontsize=8)

plt.xticksとplt.yticksは新規追加。
それ以外は、引数にfontsizeを追加しているだけです。
下がコードの実行結果です。

ラベルやタイトルの文字サイズを変更した棒グラフ

Matplotlibで縦軸(y軸)ラベルの表示(位置・角度)を指定する

初級レベルの棒グラフに対して、最も違和感を感じたところです。
段階を踏んでいきます。

y軸ラベルの表示を横書きにする

引数にrotationを追加します。

plt.ylabel("議席数", fontsize=8, rotation=0)

その結果は以下。

y軸ラベルを横書きに変更した棒グラフ

y軸ラベルが、横書きになりました。
あとは、表示位置です。

y軸ラベルの表示位置を変更する

ax = fig.add_subplot(111)のすぐ下に以下を追加します。

ax.yaxis.set_label_coords(-0.07,1.08)

set_label_coords(x, y)
左下が(0, 0)です。
よって、y軸上(左上)は(0,1)ということですね。

変更した結果は以下。

y軸のラベルを横書きに変更して位置調整した棒グラフ

スバラシイ!!
これで、違和感がなくなりました。

Matplotlibで目盛りの間隔を指定する

目盛りを意図的に追加するのは以下のコードだけです。
yscaleはlist型でデータです。
ちなみにデータ中身は、[ 0 50 100 150 200 250 300]です。

plt.yticks(yscale, fontsize=8)

以下はyscaleを作成するロジックです。
ここの説明は省きます。

scale_num = 50
y_max = (math.ceil(max(values) / scale_num) + 1 ) * scale_num
yscale = np.arange(0, y_max, scale_num)

plt.xticks(fontsize=8)
plt.yticks(yscale, fontsize=8)

なお、math.ceilを利用するために、以下行頭に追加。

import math

これらの変更後に実行した結果。

目盛りを指定した棒グラフ

y軸の目盛りが、300まで表示されるようになりました。

Matplotlibでグラフの余白を指定する

ここまでの修正をまとめます。

import math
import numpy as np
import matplotlib.pyplot as plt

labels = ['EUL', 'PES', 'EFA', 'EDD', 'ELDR', 'EPP', 'UEN', 'その他']
values = [39, 200, 42, 15, 67, 276, 27, 66]
lefts = np.arange(len(values))

fig = plt.figure(dpi=100, figsize=(4.8,2.4))
ax = fig.add_subplot(111)
ax.yaxis.set_label_coords(-0.07,1.08)

plt.bar(lefts, values, tick_label=labels, width=0.5, color="#b2b2b2")
plt.title("欧州議会選挙 2004", fontsize=9)
plt.xlabel("政党", fontsize=8)
plt.ylabel("議席数", fontsize=8, rotation=0)

scale_num = 50
y_max = (math.ceil(max(values) / scale_num) + 1 ) * scale_num
yscale = np.arange(0, y_max, scale_num)

plt.xticks(fontsize=8)
plt.yticks(yscale, fontsize=8)
plt.gca().spines['right'].set_visible(False)
plt.gca().spines['top'].set_visible(False)

ここからは、実際に画像を保存します。
表示される画像と実際に保存する画像とで余白の扱いが異なります。
Anacondaでは、その事象を確認できています。

Jupyter Notebookで開発している場合は、どうなのでしょうか?
少し気になりますが、また機会があれば検証してみます。

よって、以下のコードを最終行に追加します。
余白がわかりやすいように、背景に「red」を指定。

plt.savefig("result.png", facecolor="red")

実際に保存された画像は以下。

余白に余裕がない棒グラフ

余白の調整が必要です。
x軸のラベルである「政党」が、ほぼ見えません。
上の余白は狭いですね。

これを調整するのが、次のコードです。

plt.subplots_adjust(left=0.11, right=0.9, bottom=0.2, top=0.8)
plt.savefig("result.png", facecolor="red")

plt.subplots_adjustを画像保存の直前に追加しています。
これも基準は、左下(0, 0)です。

調整した結果は、以下。

余白を調整した棒グラフ

求める画像(wikipedeiaの棒グラフ)に近づきました。
ただ、タイトル「欧州議会選挙 2004」の位置が違いますね。。。

Matplotlibでグラフにではなく図全体にタイトルをつける

グラフにタイトルをつける場合は、位置の微調整ができません。
せいぜい、左端・中央・右端のどれかを選択できる程度です。

よって、グラフのタイトルは利用しません。
その代わりに、図全体のタイトルを利用することにします。

fig = plt.figureの下にfig.suptitleを追加。

fig = plt.figure(dpi=100, figsize=(4.8,2.4))
fig.suptitle('欧州議会選挙 2004', fontsize=9, y=0.93)

そして、plt.titleを無効(コメント)にします。

#plt.title("欧州議会選挙 2004", fontsize=9)

また、画像の余白の背景色を「white」に変更しておきます。
これにて完成です。

完成コードは以下。

import math
import numpy as np
import matplotlib.pyplot as plt

labels = ['EUL', 'PES', 'EFA', 'EDD', 'ELDR', 'EPP', 'UEN', 'その他']
values = [39, 200, 42, 15, 67, 276, 27, 66]
lefts = np.arange(len(values))

fig = plt.figure(dpi=100, figsize=(4.8,2.4))
fig.suptitle('欧州議会選挙 2004', fontsize=9, y=0.93)
ax = fig.add_subplot(111)
ax.yaxis.set_label_coords(-0.07,1.08)

plt.bar(lefts, values, tick_label=labels, width=0.5, color="#b2b2b2")
#plt.title("欧州議会選挙 2004", fontsize=9)
plt.xlabel("政党", fontsize=8)
plt.ylabel("議席数", fontsize=8, rotation=0)

scale_num = 50
y_max = (math.ceil(max(values) / scale_num) + 1 ) * scale_num
yscale = np.arange(0, y_max, scale_num)

plt.xticks(fontsize=8)
plt.yticks(yscale, fontsize=8)
plt.gca().spines['right'].set_visible(False)
plt.gca().spines['top'].set_visible(False)

plt.subplots_adjust(left=0.11, right=0.9, bottom=0.2, top=0.8)
plt.savefig("result.png", facecolor="white")

このコードの実行結果は、以下の画像となります。

最終的に完成した棒グラフの画像

求める画像(wikipedeiaの棒グラフ)とほぼ同じになりました。
ここまで似せるのは、それなりに大変でした。

しかし、1度理解すれば、このようなノウハウは使いまわし可能です。
Matplotlibで折れ線グラフを作成する場合でも、何とかなりそうですね。

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