PythonTableauTech Blog 

Matplotlib-2軸グラフの書き方

今回は、matplotlibの2軸を使ってグラフを書く方法について説明していきたいと思います。
下のグラフ作成を目標にして進めていきます。
Matplotlib-2軸グラフの書き方_1

matplotlibについて

matplotlibは、Pythonでグラフ描画をする際に使われるライブラリ です。今回の記事では、matplotlibを使い、オブジェクト指向でのグラフプロットで2軸グラフの作成例を紹介したいと思います。

matplotlibにはpyplotを使った対話形式でグラフ作成も可能です。1つのグラフだけを描画したり、そのグラフの目盛りや軸の範囲などを1つ1つ設定していくだけならよいのですが、複数のグラフを並べて表示したり、各グラフに別々な設定をしたい時などはオブジェクト指向のやり方を活用する方が何かと便利です。以下実例に沿ってやり方の紹介をします。

今回の使用環境ですが、PCはmacを使用、Pythonと各モジュールのバージョンは以下の通りです。

Python 3.9.6
matplotlib 3.4.3
numpy 1.21.2
pandas 1.3.2

使用するデータ

データは、気象庁のサイトから東京都の2020年各月の平均気温と降水量のデータを取得して、2軸グラフの元データとして使用します。「月」「月平均気温」「月間降水量」の3列×12ヵ月分のcsv形式のデータファイルで、「月」列は各月最初の日になっています。
以下はデータ読み込みのスクリプトです。read_csvコマンドでデータの0列目、つまり「月」列をインデックスとして指定し、parse_dates=Trueでインデックに指定した「月」列をdatetime型に変換しています。

import pandas as pd
import numpy as np
import datetime
df=pd.read_csv("data_2020.csv",index_col=0, parse_dates=True)
print(df.head(len(df)))
print()
print(df.index)
type(df.index)

読み込み結果は以下の通りです。

Matplotlib-2軸グラフの書き方_2

描画

2つのグラフをまとめて描画・1軸
では、さっそくグラフを書いてみましょう。まずは1軸のグラフです。figureオブジェクトでグラフを描画するための領域を設定しておきます。ちょうどプレゼンや報告書作成用のファイルを作成する時に、グラフの体裁や配置を決めて、白紙のページの所定の位置に貼り付けていく作業をプログラミングで実現しているような感じが当てはるかと思います(ちなみにmatplotlibで描画するグラフのタイトル、軸名、凡例の日本語表記対応のため、japanize_matplotlibを使用しています)。

Matplotlib-2軸グラフの書き方_3

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
import japanize_matplotlib
%matplotlib inline
fig = plt.figure()
ax1 = fig.add_subplot(111) #1行目1列の1番目という意味
# 1つのaxesオブジェクトのlines属性に2つのLine2Dオブジェクトを追加
ax1.plot(df.index.to_pydatetime(), df["月平均気温"],
        color=cm.Set1.colors[1], label="月平均気温(℃)")
ax1.plot(df.index.to_pydatetime(), df["月間降水量"],
        color=cm.Set1.colors[0], label="月間降水量(mm)")
plt.tick_params(labelsize = 10) #目盛線ラベルのフォントサイズ
#グラフタイトルを付ける
plt.title("2020年各月の平均気温と降水量の推移", fontsize=15)
#凡例をつける
ax1.legend()

どうでしょうか、赤い折れ線の降水量に比べると、青い折れ線の気はには年間を通じて大きな動きが無いようにも見えてしまいます。このグラフの問題点は「異なる単位のデータを同じ軸で表示している」という点です。気温(℃)と降水量(mm)はそもそも単位が異なり、1本の同じ軸のグラフで表すのは無理があることは言うまでもありません。

2つの数値を別々のグラフに分けて描画してみましょう。

気温と降水量のグラフを別々に描画

2つのグラフに別々に描画した結果です。

Matplotlib-2軸グラフの書き方_4

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
import japanize_matplotlib
%matplotlib inline
fig = plt.figure()
fig, ax1 = plt.subplots()
ax2 = ax1.twinx()
# 2軸グラフの本体設定
ax1.plot(df.index.to_pydatetime(), df["月平均気温"],
        color=cm.Set1.colors[1], label="月平均気温(℃)")
ax2.bar(df.index.to_pydatetime(), df["月間降水量"],
        color=cm.Set1.colors[0], alpha=0.4, width=25, label="月間降水量(mm)")
plt.tick_params(labelsize = 10) #目盛線ラベルのフォントサイズ
#グラフタイトルを付ける
plt.title("2020年各月の平均気温と降水量の推移", fontsize=15)
# 凡例表示のため、handler1と2にはグラフオブジェクトのリスト情報が入る
# label1と2には、凡例用の各labelのリスト情報が入る
handler1, label1 = ax1.get_legend_handles_labels()
handler2, label2 = ax2.get_legend_handles_labels()
# 凡例をまとめて出力する
ax1.legend(handler1 + handler2, label1 + label2, loc=2, borderaxespad=0.)
tempature_max = 10 + max(df["月平均気温"])
rainfall_max = 1.2 * max(df["月間降水量"])
ax1.set_ylim([0, tempature_max])
ax2.set_ylim([0, rainfall_max])

傾向の見えにくかった青い折れ線の月平均気温については、冬の1月から真夏の8月にかけて上昇してから9月から12月にかけてまた月の平均気温が下がっていく傾向が確認できました。

平均気温と降水量のグラフをまとめて描画・2軸

上記2つのグラフを別々なグラフとして描画するのではなく、1つのグラフにまとめて描画してみましょう。

Matplotlib-2軸グラフの書き方_5

​​import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
import japanize_matplotlib
%matplotlib inline
 
fig, ax1 = plt.subplots()
ax2 = ax1.twinx()
# 2軸グラフの本体設定
ax1.plot(df.index.to_pydatetime(), df["月平均気温"],
        color=cm.Set1.colors[1], label="月平均気温(℃)")
ax2.bar(df.index.to_pydatetime(), df["月間降水量"],
        color=cm.Set1.colors[0], alpha=0.4, width=25, label="月間降水量(mm)")
plt.tick_params(labelsize = 10) #目盛線ラベルのフォントサイズ
#グラフタイトルを付ける
plt.title("2020年各月の平均気温と降水量の推移", fontsize=15)
# 凡例の表示のため、handler1と2にはグラフオブジェクトのリスト情報が入る
# label1と2には、凡例用に各labelのリスト情報が入る
handler1, label1 = ax1.get_legend_handles_labels()
handler2, label2 = ax2.get_legend_handles_labels()
# 凡例をまとめて出力する
ax1.legend(handler1 + handler2, label1 + label2, loc=2, borderaxespad=0.)
temperature_max = 10 + max(df["月平均気温"])
rainfall_max = 1.2 * max(df["月間降水量"])
ax1.set_ylim([0, temperature_max])
ax2.set_ylim([0, rainfall_max])

ここで登場しているのが、twinx()関数です。この関数で左(ax1)と右(ax2)の軸に連関を持たせた上で、左右で異なる軸を持つことができるようになります。

おまけ: 2軸グラフを書く際に注意すべきこと

2軸グラフは使い方によっては、わかりにくくなり誤解を招くことがあります。

より重要な数値を左軸にする、なるべく違うタイプのグラフ(例えば棒グラフと折れ線グラフ)の組み合わせにする、違う色で着色して区別するなどの工夫は基本となるでしょう。この基本を踏まえて、さらにグラフへ修正を加えてみました。

Matplotlib-2軸グラフの書き方_6

左と右の縦軸の色を、対応する各グラフの色に合わせ左の気温は青色、右の降水量は赤色にしました。加えて左右の縦軸の数字にそれぞれの単位(℃とmm)を追加しました。

以下、ソースコードです。

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from matplotlib.ticker import MaxNLocator
import matplotlib.ticker as ticker
import japanize_matplotlib
%matplotlib inline
 
fig, ax1 = plt.subplots()
ax2 = ax1.twinx()
# styleを適用している場合はgrid線を片方消す
ax1.grid(True)
ax2.grid(False)
# グラフのグリッドをグラフの本体の下にずらす
ax1.set_axisbelow(True)
# 色の設定
color_1 = cm.Set1.colors[1]
color_2 = cm.Set1.colors[0]
# 2軸グラフの本体設定
ax1.plot(df.index.to_pydatetime(), df["月平均気温"],
        color=color_1, label="月平均気温(℃)")
ax2.bar(df.index.to_pydatetime(), df["月間降水量"],
        color=color_2, alpha=0.4, width=25, label="月間降水量(mm)")
#         color=color_2, alpha = 0.4, label="月間降水量(mm)")
plt.tick_params(labelsize = 10) #目盛線ラベルのフォントサイズ
#グラフタイトルを付ける
plt.title("2020年各月の平均気温と降水量の推移", fontsize=15)
# 軸の目盛りの最大値をしている
# axesオブジェクトに属するYaxisオブジェクトの値を変更
ax1.yaxis.set_major_locator(MaxNLocator(nbins=5))
ax2.yaxis.set_major_locator(MaxNLocator(nbins=5))
# 軸の縦線の色を変更している
# axesオブジェクトに属するSpineオブジェクトの値を変更
# 図を重ねてる関係で、ax2のみいじる。
ax2.spines['left'].set_color(color_1)
ax2.spines['right'].set_color(color_2)
# 軸の縦線の色を変更している
ax1.tick_params(axis='y', colors=color_1)
ax2.tick_params(axis='y', colors=color_2)
#  軸の目盛りの単位を変更する
ax1.yaxis.set_major_formatter(ticker.FormatStrFormatter("%d℃"))
ax2.yaxis.set_major_formatter(ticker.FormatStrFormatter("%dmm"))
# 凡例
# グラフの本体設定時に、ラベルを手動で設定する必要があるのは、barplotのみ。plotは自動で設定される>
handler1, label1 = ax1.get_legend_handles_labels()
handler2, label2 = ax2.get_legend_handles_labels()
# 凡例をまとめて出力する
ax1.legend(handler1 + handler2, label1 + label2, loc=2, borderaxespad=0.)
temperature_max = 10 + max(df["月平均気温"])
rainfall_max = 1.2 * max(df["月間降水量"])
ax1.set_ylim([0, temperature_max])
ax2.set_ylim([0, rainfall_max]

matplotlibで2軸グラフを描く方法をご紹介いたしました。色々な機能があるmatplotlibは、調べてみるとまた新たな発見があるかもしれません。今回の記事が皆さんのお役に立てれば幸いです。

matplotlibの関連記事はこちら

matplotlibの日本語文字化けを解消する(Mac編)

このページをシェアする:



DATUM STUDIOは、クライアントの事業成長と経営課題解決を最適な形でサポートする、データ・ビジネスパートナーです。
データ分析の分野でお客様に最適なソリューションをご提供します。まずはご相談ください。