Matplotlibでグラフを描いても、日本語がうまく表示されないことがあります。その原因と、日本語を表示するいろいろな方法について見ていきましょう。
本シリーズ「Pythonデータ処理入門」は、Pythonの基礎をマスターした人を対象に以下のような、Pythonを使ってデータを処理しようというときに便利に使えるツールやライブラリ、フレームワークの使い方の基礎を説明するものです。
なお、【Matplotlib超入門:pyplot編】では以下のバージョンを使用しています。
なお、以下ではMatplotlibのpyplotインタフェースやNumPy、pandasをインポートする以下の行を実行してあるものとします。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
まずは以下のコードを見てください。
def plot():
plt.plot([1, 2, 3], [1, 4, 9])
plt.title('グラフのタイトル')
plt.xlabel('X軸')
plt.ylabel('Y軸')
plt.text(2, 5, '日本語のテキスト')
plt.show()
plot()
これはX軸とY軸のラベルや、タイトル、コメントを日本語で記したグラフを描画する関数plotを定義して、それを呼び出すものです。筆者の手元の環境でこれを実行したら、次のようになりました。
うまく日本語が表示できていませんね。これはMatplotlibが日本語に対応していないからではなく、そのデフォルト設定では日本語には対応していないフォントがテキスト表示に使われるようになっているからです。
Matplotlibでのグラフ周りのフォント設定はpyplot.rcParams属性に辞書の形で保存されています。主に使用するのは以下といえるでしょう。
これらのフォント設定を調べてみましょう。
font_family = plt.rcParams['font.family']
font_sans_serif = plt.rcParams['font.sans-serif']
font_serif = plt.rcParams['font.serif']
font_cursive = plt.rcParams['font.cursive']
font_fantasy = plt.rcParams['font.fantasy']
font_monospace = plt.rcParams['font.monospace']
print('family:', font_family)
print('sans serif:', font_sans_serif)
print('serif:', font_serif)
print('cursive:', font_cursive)
print('fantasy:', font_fantasy)
print('monospace:', font_monospace)
ここでは上に挙げた2つのフォント設定に加えて、セリフ系の(装飾のある)フォントを指定するrcParams['font.serif']、筆記体のようなフォントを指定するrcParams['font.cursive']、装飾が多めのフォントを指定するrcParams['font.fantasy']、等幅フォントを指定するrcParams['font.monospace']についても調べています(が、それらについてはここでは深く取り上げません)。
実行結果を以下に示します。
フォントファミリーは'sans-serif'になっているので、rcParams['font.sans-serif']の設定が使われると考えればよいでしょう。この場合は、rcParams['font.sans-serif']の値である['DejaVu Sans', 'Bitstream Vera Sans', ……, 'Avant Garde', 'sans-serif']というリストの中からシステムにインストールされているものが使われます(リストの前方にあるものが優先的に使われます)。そして、このリストに日本語の書体を持ったフォントがないので、先ほどのように文字表示がうまくいかなかったのです。
ということは、日本語が表示されるようにMatplotlibを設定する一番簡単な方法は、rcParams['font.family']かrcParams['font.sans-serif']に日本語フォントを指定してやることです。自分が使っているシステムにインストール済みのフォントであれば、'Meiryo'などのように指定できます。
plt.rcParams['font.sans-serif'] = ['Meiryo']
ここでやっているように、フォント名はリストの形で複数与えます。複数のフォントを指定する例を以下に示します(「pyplot.rcParams['font.family'] = ['sans-serif']」行はデフォルトの設定なので不要ですが、ここでは明示しています)。
plt.rcParams['font.family'] = ['sans-serif']
plt.rcParams['font.sans-serif'] = ['Hiragino Maru Gothic Pro', 'Meiryo']
plot()
ここではmacOSにバンドルされている「ヒラギノ丸ゴPro」とWindowsでよく使われる「メイリオ」をpyplot.rcParams['font.sans-serif']に指定しています。macOSでこのコードを実行すると、次のように丸みを帯びたゴシック体でグラフのテキストが描画されます。
一方、Windowsでこれを実行した場合、「メイリオ」が使われるのでヒラギノ丸ゴProほどには丸みを帯びていない文字が使われます。
なお、Hiraginoフォントについては「Hiragino sans」というフォント名を指定することも可能です。これは(少なくとも筆者の手元の環境では)「ヒラギノ角ゴシックW4」にマッピングされていました。フォント名と実際のフォントの対応を調べるには以下のようなコードが使えます。
from matplotlib import font_manager
font_name = 'Hiragino sans'
font_prop = font_manager.FontProperties(family=font_name)
font_file = font_manager.findfont(font_prop)
print(f'{font_name} -> {font_file}')
ここでインポートしているmatplotlib.font_managerモジュールはフォントの検索、管理、使用についての機能やクラスがまとめられています。例えば、フォントの属性(フォントファミリー、スタイル、異体、フォントの伸縮、ウェイト、サイズ)などの情報を格納したり、操作したりするためのクラスであるFontPropertiesクラスや、FontPropertiesオブジェクトが表すフォントに最も近いフォントファイルのファイルパスを検索するfindfont関数などがあります。何より、TrueTypeフォントやAdobeのAFMフォントの一覧を管理するFontManagerクラスがあります。
上のコードではそれらのうちのFontPropertiesクラスのインスタンスを生成していますが、そのときには上で述べた「Hiragino sans」というフォント名(フォントファミリー名)を与えています。そして、生成したFontPropertiesオブジェクトが表すフォント属性に最も近いフォントファイルのパスをfindfont関数で検索し、その結果をprint関数で出力しています。
macOS上でこれを実行した結果を以下に示します(Windows上で実行すると、多くの場合はフォントが見つからずに「DejaVu sans」にフォールバックされます)。
font_managerモジュールについてはこの後でもちょっと取り上げます。
なお、ここではpyplot.rcParams['font.sans-serif']の値を変更していましたが、以下のようにpyplot.rcParams['font.family']の値を直接書き換えてしまっても構いません。
plt.rcParams['font.family'] = ['Meiryo']
これは実は以下のコードと同じことをします。
import matplotlib
matplotlib.rc('font', family='Meiryo')
ところで、今見たフォント名はどうやって調べるんだ? と思うかもしれません(フォント名が分からなければ指定のしようがありませんから)。Matplotlibで使用できるフォントの一覧はmatplotlib.get_cachedir関数で取得できるディレクトリにfontlist-<バージョン>.jsonファイルとして記述されています(以下では簡単に「fontlist.json」と書くことにします)。
import matplotlib
matplotlib.get_cachedir()
Windowsでこのコードを実行した結果を以下に示します。
ここで得られたのは「'C:\\Users\\syngs\\.matplotlib'」というディレクトリです(これは「%USERPROFILE%\.matplotlib」ディレクトリに相当します)。以下はエクスプローラーでこのディレクトリを開いて、そこにあるfontlist-v390.jsonファイルをメモ帳に読み込んだところです(なぜかMatplotlib 3.10のfontlist.jsonファイルはできていなかったので別のバージョンのものを使用しています)。
上の画像では「meiryo」という文字列を検索しています。検索で見つかった部分にある"fname"要素がフォントファイルのパスを示し、"name"要素が先ほどのpyplot.rcParams['font.family']やpyplot.rcParams['font.sans-serif']に与えるフォント名です。
自分で何かのフォントを使ってグラフにテキストを表示したいときには、このfontlist.jsonファイルを調べるとよいでしょう。
あるいはプログラムとして、次のようにして日本語のフォントの名前を一覧することも可能です。ここでは先ほど紹介したfont_managerモジュールが公開しているFontManagerクラスのシングルトンインスタンスであるfont_manager.fontManagerを使います。
from matplotlib import font_manager
available_fonts = [f.name for f in font_manager.fontManager.ttflist]
jfont_list = ['Hiragino', 'Meiryo', 'Gothic', 'Mincho']
result = []
for font in available_fonts:
for jfont in jfont_list:
if jfont in font:
result.append(font)
break
print(result)
font_manager.fontManagerオブジェクトにはttflist属性があり、ここには使用可能なフォントが列挙されています。それを反復して、フォント名を取り出したのがavailable_fontsです。また、jfont_listには日本語フォントに使われていそうな語を挙げています(日本語フォント以外でも使われている語も含まれている点には注意)。
そして、両者を反復しながら、jfont_listの各要素が利用可能なフォントの名前に含まれていれば、まあ日本語フォントではなかろうかと判断して、そのフォント名をresult(日本語フォントの名前を格納するリスト)に追加しています。例えば、available_fontsの要素として反復されたのが'Hiragino sans'で、jfont_listの要素として反復されたのが'Hiragino'であれば、「'Hiragino' in 'Hiragino sans'」がTrueになるので、resultにこれを追加するといった具合です。
また、font_managerモジュールにはfindSystemFonts関数もあります。これはシステムにインストールされているフォントを検索してくれます。これを使って同様なことも行えるかもしれません。
ただし、既に述べたように「Gothic」は日本語フォントでも他の言語のフォントでも使われているので、実際には日本語フォントだけに限定できていないはずです。逆にjfont_listに列挙していない語だけで構成された日本語フォント名はこのチェックから抜け落ちることになることにもなります。実際に自分で同様なコードを実行する場合には気を付けてください(jfont_listを自分の必要に応じて調整しましょう)。
先ほど紹介したfontlist.jsonファイルにはフォントファイルのパスが記述されていました。このようなパスを与えて、グラフに記載するテキストのフォントを指定することも可能です。
これには先ほども見たfont_managerモジュールのFontPropertiesクラスを使います。といっても、このクラスのインスタンスを生成する際にフォントファイルのパスを与えるだけです。後は、グラフのタイトルやラベルの描画時にそのオブジェクトをフォント情報として指定します。
例えば、以下はWindowsとmacOSで異なるファイルパスを与えて、フォントを指定する例です。
import sys
from matplotlib import font_manager
if sys.platform == 'darwin':
font_path = '/System/Library/Fonts/ヒラギノ角ゴシック W4.ttc'
elif sys.platform == 'win32':
font_path = r'C:\Windows\Fonts\meiryo.ttc'
font_prop = font_manager.FontProperties(fname=font_path)
plt.plot([1, 2, 3], [1, 4, 9])
plt.title('グラフのタイトル', fontproperties=font_prop)
plt.xlabel('X軸', fontproperties=font_prop)
plt.ylabel('Y軸', fontproperties=font_prop)
plt.text(2, 5, 'グラフに日本語でテキストを追加(意味はない)', fontproperties=font_prop)
plt.legend(['データ1'], prop=font_prop)
plt.show()
ここではsysモジュールのplatform属性の値を基にmacOSでは「ヒラギノ角ゴシック W4」フォントを、Windowsでは「メイリオ」フォントを使うようにフォントファイルのパスを指定しています。そして、それらを基にFontPropertiesオブジェクトを作成します。
作成したオブジェクトはpyplot.title関数やpyplot.xlabel関数などの呼び出し時にfontpropertiesパラメーターまたはpropパラメーターの値として渡します(pyplot.legend関数ではpropパラメーターになっている点に注意)。これにより、次のようなグラフが描画されます(グラフを描画するコードは、fontpropertiesパラメーターの指定を除けば、最初に定義したplot関数と同じです)。
フォントファイルのパスを直接指定したい場合には、このような方法を採ることになるでしょう。ですが、フォント名を推測できるのであれば、FontPropertiesオブジェクトの生成時にフォント名を与えてしまうのが簡単そうな気が筆者にはしました。
また、常に日本語を使ったグラフ描画を行いたければ、.matplotlibrcファイルに設定を記述しておくことも可能です(これについては説明を省略します)。
ここまではpyplot.rcParams属性を使って日本語フォントを指定する、font_managerモジュールのFontPropertiesクラスを使って日本語フォントを指定する、という2つのやり方を見てきました。
これらの方法は他に何も必要とせずに、グラフ内に日本語を含められるのでカンタンといえばカンタンです。が、多少のコードは必要になります。
これをほんの1行のimport文だけで済ませられるモジュールがあります。それが「japanize-matplotlib」です。Pythonに標準で付属するわけではないので、前もってpipなどでインストールしておく必要がある点には注意してください(例:「pip3 install japanize-matplotlib」「py -3.13 -m pip install japanize-matplotlib」)。
また、Python 3.12以降ではjapanize-matplotlibが内部的に使用しているdistutilsモジュールが標準で添付されなくなり、distutilsモジュール自体もsetuptoolsモジュールに統合されました。そのため、Python 3.12以降では「pip3 install setuptools」「py -3.13 -m pip install setuptools」などのコマンドでsetuptoolsモジュールをインストールしておく必要もあります。
インストールができたら、後はmatplotlibをインポートした後に、japanize_matplotlibをインポートして、普通にグラフを描画するだけです(モジュール名にハイフンは使えないので、「japanize_matplotlib」と2つの単語をアンダーバーでつないでいる点に注意)。
import matplotlib.pyplot as plt
import japanize_matplotlib
def plot():
plt.plot([1, 2, 3], [1, 4, 9])
plt.title('グラフのタイトル')
plt.xlabel('X軸')
plt.ylabel('Y軸')
plt.text(2, 5, '日本語のテキスト')
plt.show()
plot()
実行結果を以下に示します。
これまでにダラダラと、日本語を表示するためにやってきたことはなんなんだったんだと思うくらいにカンタンに日本語が表示できました。とはいえ、ここまでに紹介してきたことが無駄かというとそういうわけでもありません。japanize-matplotlibは内部で次のようなコードを実行しています。
matplotlib.rc('font', family=FONT_NAME)
この記事で既に紹介したmatplotlib.rc関数が使われていることが分かります。つまり、これまでに見てきたようなことを自動で行ってくれるのがjapanize-matplotlibモジュールというわけです。遠回りをして、最後に一番簡単な方法にたどり着きましたが、そこまでの内容を見てきたからこそ、なぜ簡単になっているのかが理解できるのが今回のポイントです。
取りあえず、pyplot編はこの辺で終わることにします。本当はこの記事でseabornとの連携についても触れようと思っていたのですが、これについては次の「オブジェクト指向インタフェース編」の中で取り上げることにしましょう。
Copyright© Digital Advantage Corp. All Rights Reserved.