最近では、材料工学などの分野でもビッグデータ解析や機械学習をする機会が増えてきています。その際、Pythonなどを利用する機会もあるかと思います。解析した結果を図として論文やパワポ、ポスターなどに載せたいとき、普段ならOriginやNgraphなどのグラフ作成ソフトを使用しますが、せっかくならPythonのMatplotlibで綺麗に描きたいという気持ちになるはずです。そんな気持ちで色々調べた結果をまとめました。
以下に示す例は、完全に'私の主観'で綺麗と言っているので、違うと思ったら変更してください。
ライブラリの読み込み
まずは最低限必要なライブラリを読み込んでおきます。
import numpy as np
import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler()
%matplotlib inline
図の初期設定
フォント周りの設定
rcParamsでデフォルトを設定しておくことで、メインの描画プログラムで複雑に指定しなくてよくなります。ここでは、フォントのサイズや種類を設定しています。'serif'がひげ飾りを意味するようで、いわゆる明朝系を表して、'sans-serif'はひげ飾りがないということでゴシック系を表しています。論文では、文章は明朝系、図はゴシック系という場合が多いのでここではゴシック系にしています。
plt.rcParams['font.size'] = 8
plt.rcParams['font.family']= 'sans-serif'
plt.rcParams['font.sans-serif'] = ['Arial']
軸周りの設定
ここでは軸周りの設定をしています。私が思う美しい図は、tickの方向は内向きで、線は少し太め、薄いグリッドが破線で入っている(←グリッドは場合によりますが)ものです。そのようになるような設定が以下の通りです。
plt.rcParams['xtick.direction'] = 'in'
plt.rcParams['ytick.direction'] = 'in'
plt.rcParams['xtick.major.width'] = 1.2
plt.rcParams['ytick.major.width'] = 1.2
plt.rcParams['axes.linewidth'] = 1.2
plt.rcParams['axes.grid']=True
plt.rcParams['grid.linestyle']='--'
plt.rcParams['grid.linewidth'] = 0.3
凡例周りの設定
続いて、凡例について。markerscaleはmarkerサイズの倍率、fancyboxは角の丸み、framealphaは凡例の透明度、edgecolorは凡例の枠線の色です。私が思う美しい凡例は、角張った四角形で、透明化はされておらず、枠線は黒色です。そのようにする設定が以下の通りです。
plt.rcParams["legend.markerscale"] = 2
plt.rcParams["legend.fancybox"] = False
plt.rcParams["legend.framealpha"] = 1
plt.rcParams["legend.edgecolor"] = 'black'
図の描画例
データセットの取得および前処理
カリフォルニア大学アーバイン校がhttp://archive.ics.uci.edu/ml/index.php 機械学習やデータマイニングに関するデータを配布してくれています。ここでは例として、その中からWineデータセットを使用します。以下では、pandasでのcsvの読み込み、カラム名の設定を行なっています。
df_wine = pd.read_csv('https://archive.ics.uci.edu/''ml/machine-learning-databases/wine/wine.data',header=None)
df_wine.columns = ['Class label', 'Alcohol', 'Malic acid', 'Ash','Alcalinity of ash', 'Magnesium', 'Total phenols','Flavanoids',
'Nonflavanoid phenols', 'Proanthocyanins','Color intensity', 'Hue', 'OD280/OD315 of diluted wines','Proline']
データの中身は、こんな感じです。178ワインサンプルに対して、そのクラス(品種)と13列の特徴量のデータセットになります。
df_wine.head()
Class label | Alcohol | Malic acid | Ash | Alcalinity of ash | Magnesium | Total phenols | Flavanoids | Nonflavanoid phenols | Proanthocyanins | Color intensity | Hue | OD280/OD315 of diluted wines | Proline | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 14.23 | 1.71 | 2.43 | 15.6 | 127 | 2.80 | 3.06 | 0.28 | 2.29 | 5.64 | 1.04 | 3.92 | 1065 |
1 | 1 | 13.20 | 1.78 | 2.14 | 11.2 | 100 | 2.65 | 2.76 | 0.26 | 1.28 | 4.38 | 1.05 | 3.40 | 1050 |
2 | 1 | 13.16 | 2.36 | 2.67 | 18.6 | 101 | 2.80 | 3.24 | 0.30 | 2.81 | 5.68 | 1.03 | 3.17 | 1185 |
3 | 1 | 14.37 | 1.95 | 2.50 | 16.8 | 113 | 3.85 | 3.49 | 0.24 | 2.18 | 7.80 | 0.86 | 3.45 | 1480 |
4 | 1 | 13.24 | 2.59 | 2.87 | 21.0 | 118 | 2.80 | 2.69 | 0.39 | 1.82 | 4.32 | 1.04 | 2.93 | 735 |
次に、データセットを使用する前に、データセットの中に欠測データがないか確認してみます。
df_wine.isna().sum()
Class label 0
Alcohol 0
Malic acid 0
Ash 0
Alcalinity of ash 0
Magnesium 0
Total phenols 0
Flavanoids 0
Nonflavanoid phenols 0
Proanthocyanins 0
Color intensity 0
Hue 0
OD280/OD315 of diluted wines 0
Proline 0
上記のようにwineデータセットは、欠測データがありません。もし欠測データがある場合は以下のようにdropnaでNanを含む行を削除したり、Imputerで補完しても良いかもしれません。
df_wine = df_wine.dropna()
クラスラベルのリスト作成
今回、クラスごとに色分けした散布図を描きたいので、一旦データの中からユニークなクラスラベルの配列を取得しておきます。
classlist = np.unique(df_wine['Class label'])
classlist
array([1, 2, 3])
カラーリストの作成
ここでは、https://matplotlib.org/examples/color/named_colors.html を参考に、カラーリストを作成しています。色を変えるクラスが3つなので3つの色を選択しました。ちなみに、色覚障害の方(←人によって違うようですが)にとって、青と黄色が認識しやすいようなので、いつも"blue"と"gold"を入れています。
colorlist=["blue","gold","green"]
https://matplotlib.org/examples/color/colormaps_reference.html のカラーマップを利用する方法もあります。連続的なカラーが必要な場合にはよく利用しますが、離散的なものについては自分で決めたいので上のように自作してます。
必要なデータの抽出
df_wineをそのまま利用しても良いのですが、分析の際に必要なデータのみを抽出しておきます(処理が軽くなったり、変なミスをしにくくなったりと、処理するデータはできるだけシンプルにしたい)。ここでは、wineの品種を分類する上で重要とされる、Colorintensity、ProlineをそれぞれX、Y軸とし、Flavanoidsをマーカーサイズに設定することにしました。
df = df_wine[['Class label', 'Color intensity', 'Proline','Flavanoids']]
図の描画
figsizeやdpiは、論文やパワポ、ポスターなど用途によって変更すべきですが、だいたい解像度は300dpi以上は必要になります。
今回は、1つの画像に1つのグラフを描画していますが、1つの画像に2つの画像を描画したいときは、ax1=add_subplot(1,2,1)のような形で複数用意します。ちなみに引数は、(行、列、場所)です。
ticklabelのformatで'sci'を選ぶことで科学表記で軸の値を表示することができ、scilimitsである程度の桁の数になると自動で指数表記で外出ししてくれます。また、指数部分はデフォルトでは1e3のような表記ですが、useMathTextをTrueに設定することで10^2のような表記に変更可能です。
私が思う美しい図は、全ての軸にtickが内向きでついているものなので、ticks_positionにbothを設定しています。
ここでは、マーカーサイズのMinMax正規化を行なっています。正規化しなくても、そのままの値でマーカーサイズに設定できますが、データごとにいちいちバラつくので正規化しておくとサイズ調整が楽です。
凡例は、枠線の太さを0.3にし、anchor部分で設置位置の調整をしています。凡例内のマーカーサイズは、描画するプロットの点(最後の点?)に依存するらしく、固定されたサイズで表示するのは難しそう。とりあえず下の例では、同じサイズになるように表示されない座標にプロット点を無理やり追加しています。
ちなみに、保存の際、背景を透明化処理したい場合は、transparent=Trueを引数に入れると透明になります。
fig = plt.figure(figsize=(3,3),dpi=300,facecolor='w',edgecolor='k')
ax = fig.add_subplot(1,1,1)
ax.ticklabel_format(style="sci", scilimits=(0,0), axis="y",useMathText=True)
ax.xaxis.set_ticks_position('both')
ax.yaxis.set_ticks_position('both')
ax.set_xlabel(df.keys()[1])
ax.set_ylabel(df.keys()[2])
ax.set_xlim(1,12)
ax.set_ylim(200,1750)
ax.grid(zorder=0)
df["Marker size"] = mms.fit_transform(df.iloc[:,3].reshape(-1, 1)).T[0]
for cls in classlist:
ax.scatter(df[df['Class label']==cls].iloc[:,1], df[df['Class label']==cls].iloc[:,2],s=df[df['Class label']==cls].loc[:,'Marker size']*30,label=None, c=colorlist[cls-1],alpha = 0.8,lw=0,zorder=3)
#凡例内のマーカーは図中のプロット点に依存するようで、サイズがバラバラになる。そのため、以下のfor分は,凡例内のマーカーサイズを固定するために実装している。←他にいい方法はないのだろうか。。調査中
for cls in classlist:
ax.scatter(100000, 100000,s=7, c=colorlist[cls-1],alpha = 0.8,lw=0,label="class"+str(cls))
ax.legend(loc='upper left', bbox_to_anchor=(0.01, 0.99),fontsize=8).get_frame().set_linewidth(0.3)
fig.savefig('wine_example.png', dpi=300)
色々調べてみて感じましたが、基本的にはmatplotlibの公式で、どんな引数があるのかを確認するとだいたい思い通りに図が描けます。何事も公式を見るのが一番ですね。以下に例を示しておきます。
figure : https://matplotlib.org/api/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure.add_axes
legend : https://matplotlib.org/api/legend_api.html
grid : https://matplotlib.org/api/_as_gen/matplotlib.pyplot.grid.html
tickformat : https://matplotlib.org/api/_as_gen/matplotlib.axes.Axes.ticklabel_format.html
終わりに
今回は、有名なデータセットを使用して基本的なグラフの作成方法を示しましたが、近日中に実際に科学的データを使った応用例をまとめたいと思います。