Local Optima

数理最適化を趣味的に楽しむ。

Pythonでグラフを書く & レポートで使うには

グラフ描き、していますか。Excelでグラフを作るのも悪くはないけど、データ量が多いとか、Excelよりはもう少し気の利いたことを「何か」にやって欲しいなあというときには、Pythonが便利です。

  • Pythonを使ってグラフを描くツール
  • グラフの書式をレポート向きにカスタマイズする
  • 印刷にも適したsvg形式でグラフを保存する

以上のポイントについて書いていきます。

便利なライブラリたち

Pythonでデータ整理→データ処理→可視化の流れで私が愛用している(最近愛用しだした)ライブラリがこちらです。

  • numpy
  • pandas
  • matplotlib
  • seaborn

numpyは直接いじることはまだあまりないのですが、他のライブラリの内部で使用されるので必要です。pandasは言わずとしれたデータ整理のためのライブラリですね。pandas.DataFrameはRのDataFrameに似せた作りになっており、つまるところ多量のデータを表形式のように取り回すことができます。

ここではmatplotlibが可視化の中心的なライブラリです。いろいろな図を書くときにカスタマイズ性はかなり高い反面、細かいところに手を入れようと思うと手間がかかります。そこで、seabornの出番です。 seabornはmatplotlibのラッパ (wrapper) で、日常的なデータ可視化のタスクに使いやすいようになっており、そこそこ見た目の整ったグラフが簡単にちゃっかり(?)書けます。 Rでいうところのggplotみたいな立ち位置だと思えばよいです。

こいつらは

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

のようにimportして使うのが定番です。 なぜseabornがsnsなのか、賢明なる読者のあなたは気になることでしょう。調べてみたところ The west wing というアメリカのドラマに登場する Samuel Norman Seaborn という人物名にちなんでいるようです。

matplotlibとseabornの使い分け

seabornはmatplotlibのラッパであることから、核の部分ではmatplotlibが使われています。少し手を突っ込むとseabornで描いたグラフの特定のパーツをmatplotlibでカスタマイズするという芸当もできます。

したがって、seabornに出来ることは極力seabornにやってもらうとよいと思います。seabornにも手の届かない部分がありますが、そこは仕方がないので、途中までseabornで作って一部matplotlibで描き足す、ということをします。

基本的な使い方は…

英語を読むのがさほど苦でないのなら、公式チュートリアルを読むのが最も体系的で親切です。

matplotlib.org

seaborn.pydata.org

まずは、seabornの公式チュートリアルを遠目に眺めて、どんな図が書けそうかご覧になるとよいでしょう。ただし、seabornは実装に対してマニュアル (APIドキュメント) の説明が不足している部分が多々あります。 中長期的な習得・習熟を考えれば、matplotlibと両方バランス良く使っていくと仕様がつかめてくると思います。

いい忘れそうになりましたが、エディタとしてVisual Studio CodeでJupyter Notebookを立ち上げてPythonコードを書くのが楽だと私は思って愛用しています。VS Codeでなくてもいいと思いますが、グラフ描きはトライ&エラーを繰り返すことになるのでJupyter Notebookの類は「ほぼ必須」のレベルで重宝します。しかしここはお好みで好きなものをお使いください。

matplotlibで日本語を使うなら

ところで、matplotlibの軸ラベルなどに日本語を使おうとすると、初期設定では文字化けしてしまいます。日本語フォントを使えるように設定を変更する必要があります。

import matplotlib.pyplot as plt が済んだら、いの一番に

plt.rcParams["font-family"] = "Yu Gothic"

を書いて、游ゴシックを使うようにするとよいです。rcParamsとは、matplotlibの設定値がいろいろに書き込まれているもので、フォント以外にも軸のスタイルなど様々な項目のデフォルト値が設定できます。

レポートも見据えてseabornで最初にやっておきたい設定いろいろ

matplotlibと同様、seabornでもはじめに既定のフォント設定を上書きする必要があります。うるさいことを言わないのであれば、

sns.set(font='Yu Gothic')

これだけで見た目なんとなくいい感じのグラフになります。

ですが、いかにもseabornっぽすぎるのと、グラフの体裁がmatplotlib純製のものと合わないとか、そもそも規程に合わないとか、そういうこともあろうかと思います。そういうことに備えて、レポートなどの文書に貼ってもしっかり見栄えがするようなグラフを描くために、初期設定を他にもいろいろいじっておくとよいです。

sns.set(rc={...})は、dictの内容で背後にいるmatplotlibのrcParamsも同時に上書きできますのでこれを使います。私のオススメはこうです。

sns.set(style='white', rc={
    'axes.spines.top': False,   # 上の枠線を削除
    'axes.spines.right': False, # 右の枠線を削除
    'xtick.bottom': True,       # 横軸の目盛りを表示
    'xtick.direction': 'in',    # 目盛線は内側に
    'ytick.left': True,         # 縦軸の目盛りを表示
    'ytick.direction': 'in',
    'font.sans-serif': 'Yu Gothic', # 標準のフォントを「游ゴシック」に
    'mathtext.fontset': 'cm'        # 数学用イタリック文字の有効化
})

レポートに貼る図

レポートだろうが論文だろうが、紙資料にグラフを載せる機会はたくさんあります。しかし「画像で貼り付け」になっていては印刷の際にボヤけたりしてダサいです。

matplotlibはSVG形式ベクターデータで図を保存できる(seabornで描いた図も!)ので、これを使えば拡大・縮小しても輪郭がバチッと決まった図を印刷できます。もちろんWordにも挿入できます。

保存のときの拡張子を plt.savefig("hoge.svg") のようにsvgに設定するだけです。仕上がりは「使用例1」でごらんください。

ただし、ベクターデータも一長一短です。例えば複雑な音声波形は、データ点が多すぎるためSVGよりもむしろ解像度の高いPNG形式で保存したほうがファイルサイズが小さく済むこともあります。

使用例1

かんたんに作った複数列のDataFrameをグラフにし、svgで保存します。

seabornでスタイリングを設定しておき、グラフ描画自体はpandas.DataFrame.plot()を使用しました。

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

sns.set(style='white', rc={
    'axes.spines.top': False,   # 上の枠線を削除
    'axes.spines.right': False, # 右の枠線を削除
    'xtick.bottom': True,       # 横軸の目盛りを表示
    'xtick.direction': 'in',    # 目盛線は内側に
    'ytick.left': True,         # 縦軸の目盛りを表示
    'ytick.direction': 'in',
    'font.sans-serif': 'Yu Gothic', # 標準のフォントを「游ゴシック」に
    'mathtext.fontset': 'cm'        # 数学用イタリック文字の有効化
})

x = np.arange(-6.28, 6.28, 0.05)
df = pd.DataFrame({'sin':np.sin(x), 'cos':np.cos(x), 'ロジ': 1/(1+np.exp(-x))}, index=x)

# グラフ描画
df.plot() 
plt.xlabel("横軸 $x$")
plt.ylabel("縦軸 $f(x)$")
plt.savefig("sample.svg")

図の仕上がりはこのようになります。ただし、はてなブログへの貼り付けの都合上、上記と一部異なるコードで出力しています。

<rdf:RDF xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <cc:Work> <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/> <dc:date>2020-08-16T15:01:34.562051</dc:date> <dc:format>image/svg+xml</dc:format> <dc:creator> <cc:Agent> <dc:title>Matplotlib v3.3.0, https://matplotlib.org/</dc> </cc:Agent> </dc:creator> </cc:Work> </rdf:RDF> −6 −4 −2 0 2 4 6 横軸  x −1.00 −0.75 −0.50 −0.25 0.00 0.25 0.50 0.75 1.00 縦軸 () fx sin cos ロジ

拡大・縮小して表示しても文字や線の輪郭がにじまないのがベクター形式画像の強いところです。

使用例2

seabornを使うとごちゃごちゃしたデータの可視化が便利です。seabornに同梱の、花の品種と花びらのサイズのデータセットを使った例をお示しします。

まずは公式チュートリアルと同じように、データをロードして簡単に層別して散布図にします。

iris = sns.load_dataset('iris')                # データセットをロード
grid = sns.PairGrid(iris, hue='species')
grid.map(plt.scatter)
# grid.savefig('iris.png')

f:id:local_optima:20200816162523p:plain

カラムの名前が、手元ではこれでよくても、レポートに貼るのなら変えたほうがいいかもしれません。seaborn.PairGrid.axes[i,j] で一つ一つのグラフにアクセスできます。このオブジェクトはmatplotlibのAxesのサブクラスなので、Axesで使えるメソッドなどが使えます。これを使って0列目の縦軸ラベルと3列目の横軸ラベルを書き換えます。

for i, label in enumerate(['がく長さ', 'がく幅', '花びら長さ', '花びら幅']):
    grid.axes[i,0].set_ylabel(label)
    grid.axes[3,i].set_xlabel(label)

凡例 (legend) は、seaborn.PairGrid.add_legend()で追加することができます。ただやると元々の値で凡例が入っていますが、これもカスタマイズできます。

grid.add_legend(title="品種", labels=['セトナ', 'バーシクル', 'バージニカ'])

f:id:local_optima:20200816162527p:plain

散布図行列をレポートにそのまま貼ることはそんなにない気もしますが、seabornの実装の深いところに入り込んでmatplotlibを動かすのに参考にしていただければ幸いです。

See also

seabornのリファレンスは結構ざっくりとしか書かれていません。内部の動きはGithubリポジトリでコードを読んで追いかけるのがよさそうです。matplotlibのリファレンス(こちらは丁寧です)と一緒にお読みください。

github.com